Index: /stable/HeuristicLab.ExtLibs.sln
===================================================================
--- /stable/HeuristicLab.ExtLibs.sln	(revision 17157)
+++ /stable/HeuristicLab.ExtLibs.sln	(revision 17158)
@@ -78,4 +78,8 @@
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeuristicLab.Attic", "HeuristicLab.ExtLibs\HeuristicLab.Attic\1.0.0\HeuristicLab.Attic.csproj", "{44A10478-D290-43C2-9757-F08F5292FEA5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeuristicLab.ALGLIB-3.15.0", "HeuristicLab.ExtLibs\HeuristicLab.ALGLIB\3.15.0\HeuristicLab.ALGLIB-3.15.0\HeuristicLab.ALGLIB-3.15.0.csproj", "{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ALGLIB-3.15.0", "HeuristicLab.ExtLibs\HeuristicLab.ALGLIB\3.15.0\ALGLIB-3.15.0\ALGLIB-3.15.0.csproj", "{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeuristicLab.OrTools-7.0.0", "HeuristicLab.ExtLibs\HeuristicLab.OrTools\7.0.0\HeuristicLab.OrTools-7.0.0\HeuristicLab.OrTools-7.0.0.csproj", "{AEC895BC-C519-49D3-9A94-6B1C71AD6671}"
@@ -509,4 +513,28 @@
 		{44A10478-D290-43C2-9757-F08F5292FEA5}.Release|x86.ActiveCfg = Release|Any CPU
 		{44A10478-D290-43C2-9757-F08F5292FEA5}.Release|x86.Build.0 = Release|Any CPU
+		{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}.Debug|x64.Build.0 = Debug|Any CPU
+		{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}.Debug|x86.Build.0 = Debug|Any CPU
+		{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}.Release|x64.ActiveCfg = Release|Any CPU
+		{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}.Release|x64.Build.0 = Release|Any CPU
+		{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}.Release|x86.ActiveCfg = Release|Any CPU
+		{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}.Release|x86.Build.0 = Release|Any CPU
+		{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}.Debug|x64.Build.0 = Debug|Any CPU
+		{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}.Debug|x86.Build.0 = Debug|Any CPU
+		{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}.Release|Any CPU.Build.0 = Release|Any CPU
+		{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}.Release|x64.ActiveCfg = Release|Any CPU
+		{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}.Release|x64.Build.0 = Release|Any CPU
+		{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}.Release|x86.ActiveCfg = Release|Any CPU
+		{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}.Release|x86.Build.0 = Release|Any CPU
 		{AEC895BC-C519-49D3-9A94-6B1C71AD6671}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{AEC895BC-C519-49D3-9A94-6B1C71AD6671}.Debug|Any CPU.Build.0 = Debug|Any CPU
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/ALGLIB-3.15.0.csproj
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/ALGLIB-3.15.0.csproj	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/ALGLIB-3.15.0.csproj	(revision 17158)
@@ -0,0 +1,95 @@
+﻿<?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>{BFFE7AFD-0996-4381-BF06-5D031B3E54A8}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>ALGLIB</RootNamespace>
+    <AssemblyName>ALGLIB-3.15.0</AssemblyName>
+    <TargetFrameworkVersion>v4.6.1</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>
+    <Prefer32Bit>false</Prefer32Bit>
+  </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>
+    <Prefer32Bit>false</Prefer32Bit>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <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>
+    <None Include="HeuristicLab.snk" />
+  </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: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/Properties/AssemblyInfo.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/Properties/AssemblyInfo.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/Properties/AssemblyInfo.cs	(revision 17158)
@@ -0,0 +1,32 @@
+﻿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.15.0")]
+[assembly: AssemblyDescription("ALGLIB® - numerical analysis library, 1999-2019")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("ALGLIB project")]
+[assembly: AssemblyProduct("ALGLIB-3.15.0")]
+[assembly: AssemblyCopyright("Copyright © Sergey Bochkanov (ALGLIB project) 1999-2019")]
+[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)]
+
+// 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.15.0.0")]
+[assembly: AssemblyFileVersion("3.15.0.0")]
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/alglib_info.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/alglib_info.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/alglib_info.cs	(revision 17158)
@@ -0,0 +1,22 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 >>>
+*************************************************************************/
+[assembly:System.Reflection.AssemblyCompanyAttribute("ALGLIB Project")]
+[assembly:System.Reflection.AssemblyProductAttribute("ALGLIB for C# (managed)")]
+[assembly:System.Reflection.AssemblyInformationalVersionAttribute("3.15.0.0")]
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/alglibinternal.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/alglibinternal.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/alglibinternal.cs	(revision 17158)
@@ -0,0 +1,16508 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 164
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+    public class scodes
+    {
+        public static int getrdfserializationcode(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 1;
+            return result;
+        }
+
+
+        public static int getkdtreeserializationcode(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 2;
+            return result;
+        }
+
+
+        public static int getmlpserializationcode(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 3;
+            return result;
+        }
+
+
+        public static int getmlpeserializationcode(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 4;
+            return result;
+        }
+
+
+        public static int getrbfserializationcode(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 5;
+            return result;
+        }
+
+
+        public static int getspline2dserializationcode(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 6;
+            return result;
+        }
+
+
+        public static int getidwserializationcode(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 7;
+            return result;
+        }
+
+
+        public static int getknnserializationcode(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 108;
+            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 : apobject
+        {
+            public bool[] ba0;
+            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 double[,] rm0;
+            public double[,] rm1;
+            public apbuffers()
+            {
+                init();
+            }
+            public override void init()
+            {
+                ba0 = new bool[0];
+                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];
+                rm0 = new double[0,0];
+                rm1 = new double[0,0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                apbuffers _result = new apbuffers();
+                _result.ba0 = (bool[])ba0.Clone();
+                _result.ia0 = (int[])ia0.Clone();
+                _result.ia1 = (int[])ia1.Clone();
+                _result.ia2 = (int[])ia2.Clone();
+                _result.ia3 = (int[])ia3.Clone();
+                _result.ra0 = (double[])ra0.Clone();
+                _result.ra1 = (double[])ra1.Clone();
+                _result.ra2 = (double[])ra2.Clone();
+                _result.ra3 = (double[])ra3.Clone();
+                _result.rm0 = (double[,])rm0.Clone();
+                _result.rm1 = (double[,])rm1.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Structure which is used to workaround limitations of ALGLIB parallellization
+        environment.
+
+          -- ALGLIB --
+             Copyright 12.04.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public class sboolean : apobject
+        {
+            public bool val;
+            public sboolean()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                sboolean _result = new sboolean();
+                _result.val = val;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Structure which is used to workaround limitations of ALGLIB parallellization
+        environment.
+
+          -- ALGLIB --
+             Copyright 12.04.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public class sbooleanarray : apobject
+        {
+            public bool[] val;
+            public sbooleanarray()
+            {
+                init();
+            }
+            public override void init()
+            {
+                val = new bool[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                sbooleanarray _result = new sbooleanarray();
+                _result.val = (bool[])val.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Structure which is used to workaround limitations of ALGLIB parallellization
+        environment.
+
+          -- ALGLIB --
+             Copyright 12.04.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public class sinteger : apobject
+        {
+            public int val;
+            public sinteger()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                sinteger _result = new sinteger();
+                _result.val = val;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Structure which is used to workaround limitations of ALGLIB parallellization
+        environment.
+
+          -- ALGLIB --
+             Copyright 12.04.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public class sintegerarray : apobject
+        {
+            public int[] val;
+            public sintegerarray()
+            {
+                init();
+            }
+            public override void init()
+            {
+                val = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                sintegerarray _result = new sintegerarray();
+                _result.val = (int[])val.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Structure which is used to workaround limitations of ALGLIB parallellization
+        environment.
+
+          -- ALGLIB --
+             Copyright 12.04.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public class sreal : apobject
+        {
+            public double val;
+            public sreal()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                sreal _result = new sreal();
+                _result.val = val;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Structure which is used to workaround limitations of ALGLIB parallellization
+        environment.
+
+          -- ALGLIB --
+             Copyright 12.04.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public class srealarray : apobject
+        {
+            public double[] val;
+            public srealarray()
+            {
+                init();
+            }
+            public override void init()
+            {
+                val = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                srealarray _result = new srealarray();
+                _result.val = (double[])val.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Structure which is used to workaround limitations of ALGLIB parallellization
+        environment.
+
+          -- ALGLIB --
+             Copyright 12.04.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public class scomplex : apobject
+        {
+            public complex val;
+            public scomplex()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                scomplex _result = new scomplex();
+                _result.val = val;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Structure which is used to workaround limitations of ALGLIB parallellization
+        environment.
+
+          -- ALGLIB --
+             Copyright 12.04.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public class scomplexarray : apobject
+        {
+            public complex[] val;
+            public scomplexarray()
+            {
+                init();
+            }
+            public override void init()
+            {
+                val = new complex[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                scomplexarray _result = new scomplexarray();
+                _result.val = (alglib.complex[])val.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        Internally calls SetErrorFlag() with condition:
+
+            Abs(Val-RefVal)>Tol*Max(Abs(RefVal),S)
+            
+        This function is used to test relative error in Val against  RefVal,  with
+        relative error being replaced by absolute when scale  of  RefVal  is  less
+        than S.
+
+        This function returns value of COND.
+        *************************************************************************/
+        public static void seterrorflagdiff(ref bool flag,
+            double val,
+            double refval,
+            double tol,
+            double s,
+            alglib.xparams _params)
+        {
+            alglib.ap.seterrorflag(ref flag, (double)(Math.Abs(val-refval))>(double)(tol*Math.Max(Math.Abs(refval), s)), "apserv.ap:162");
+        }
+
+
+        /*************************************************************************
+        The function always returns False.
+        It may be used sometimes to prevent spurious warnings.
+
+          -- ALGLIB --
+             Copyright 17.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool alwaysfalse(alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        The function "touches" integer - it is used  to  avoid  compiler  messages
+        about unused variables (in rare cases when we do NOT want to remove  these
+        variables).
+
+          -- ALGLIB --
+             Copyright 17.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void touchint(ref int a,
+            alglib.xparams _params)
+        {
+        }
+
+
+        /*************************************************************************
+        The function "touches" real   -  it is used  to  avoid  compiler  messages
+        about unused variables (in rare cases when we do NOT want to remove  these
+        variables).
+
+          -- ALGLIB --
+             Copyright 17.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void touchreal(ref double a,
+            alglib.xparams _params)
+        {
+        }
+
+
+        /*************************************************************************
+        The function performs zero-coalescing on real value.
+
+        NOTE: no check is performed for B<>0
+
+          -- ALGLIB --
+             Copyright 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static double coalesce(double a,
+            double b,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = a;
+            if( (double)(a)==(double)(0.0) )
+            {
+                result = b;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        The function performs zero-coalescing on integer value.
+
+        NOTE: no check is performed for B<>0
+
+          -- ALGLIB --
+             Copyright 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static int coalescei(int a,
+            int b,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = a;
+            if( a==0 )
+            {
+                result = b;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        The function convert integer value to real value.
+
+          -- ALGLIB --
+             Copyright 17.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double inttoreal(int a,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = a;
+            return result;
+        }
+
+
+        /*************************************************************************
+        The function calculates binary logarithm.
+
+        NOTE: it costs twice as much as Ln(x)
+
+          -- ALGLIB --
+             Copyright 17.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double logbase2(double x,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = Math.Log(x)/Math.Log(2);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function compares two numbers for approximate equality, with tolerance
+        to errors as large as tol.
+
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool approxequal(double a,
+            double b,
+            double tol,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = (double)(Math.Abs(a-b))<=(double)(tol);
+            return result;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            if( m>0 && n>0 )
+            {
+                if( alglib.ap.rows(x)<m || alglib.ap.cols(x)<n )
+                {
+                    x = new double[m, n];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Grows X, i.e. changes its size in such a way that:
+        a) contents is preserved
+        b) new size is at least N
+        c) new size can be larger than N, so subsequent grow() calls can return
+           without reallocation
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void bvectorgrowto(ref bool[] x,
+            int n,
+            alglib.xparams _params)
+        {
+            bool[] oldx = new bool[0];
+            int i = 0;
+            int n2 = 0;
+
+            
+            //
+            // Enough place
+            //
+            if( alglib.ap.len(x)>=n )
+            {
+                return;
+            }
+            
+            //
+            // Choose new size
+            //
+            n = Math.Max(n, (int)Math.Round(1.8*alglib.ap.len(x)+1));
+            
+            //
+            // Grow
+            //
+            n2 = alglib.ap.len(x);
+            alglib.ap.swap(ref x, ref oldx);
+            x = new bool[n];
+            for(i=0; i<=n-1; i++)
+            {
+                if( i<n2 )
+                {
+                    x[i] = oldx[i];
+                }
+                else
+                {
+                    x[i] = false;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Grows X, i.e. changes its size in such a way that:
+        a) contents is preserved
+        b) new size is at least N
+        c) new size can be larger than N, so subsequent grow() calls can return
+           without reallocation
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ivectorgrowto(ref int[] x,
+            int n,
+            alglib.xparams _params)
+        {
+            int[] oldx = new int[0];
+            int i = 0;
+            int n2 = 0;
+
+            
+            //
+            // Enough place
+            //
+            if( alglib.ap.len(x)>=n )
+            {
+                return;
+            }
+            
+            //
+            // Choose new size
+            //
+            n = Math.Max(n, (int)Math.Round(1.8*alglib.ap.len(x)+1));
+            
+            //
+            // Grow
+            //
+            n2 = alglib.ap.len(x);
+            alglib.ap.swap(ref x, ref oldx);
+            x = new int[n];
+            for(i=0; i<=n-1; i++)
+            {
+                if( i<n2 )
+                {
+                    x[i] = oldx[i];
+                }
+                else
+                {
+                    x[i] = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Grows X, i.e. appends rows in such a way that:
+        a) contents is preserved
+        b) new row count is at least N
+        c) new row count can be larger than N, so subsequent grow() calls can return
+           without reallocation
+        d) new matrix has at least MinCols columns (if less than specified amount
+           of columns is present, new columns are added with undefined contents);
+           MinCols can be 0 or negative value = ignored
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixgrowrowsto(ref double[,] a,
+            int n,
+            int mincols,
+            alglib.xparams _params)
+        {
+            double[,] olda = new double[0,0];
+            int i = 0;
+            int j = 0;
+            int n2 = 0;
+            int m = 0;
+
+            
+            //
+            // Enough place?
+            //
+            if( alglib.ap.rows(a)>=n && alglib.ap.cols(a)>=mincols )
+            {
+                return;
+            }
+            
+            //
+            // Sizes and metrics
+            //
+            if( alglib.ap.rows(a)<n )
+            {
+                n = Math.Max(n, (int)Math.Round(1.8*alglib.ap.rows(a)+1));
+            }
+            n2 = Math.Min(alglib.ap.rows(a), n);
+            m = alglib.ap.cols(a);
+            
+            //
+            // Grow
+            //
+            alglib.ap.swap(ref a, ref olda);
+            a = new double[n, Math.Max(m, mincols)];
+            for(i=0; i<=n2-1; i++)
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    a[i,j] = olda[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Grows X, i.e. appends cols in such a way that:
+        a) contents is preserved
+        b) new col count is at least N
+        c) new col count can be larger than N, so subsequent grow() calls can return
+           without reallocation
+        d) new matrix has at least MinRows row (if less than specified amount
+           of rows is present, new rows are added with undefined contents);
+           MinRows can be 0 or negative value = ignored
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixgrowcolsto(ref double[,] a,
+            int n,
+            int minrows,
+            alglib.xparams _params)
+        {
+            double[,] olda = new double[0,0];
+            int i = 0;
+            int j = 0;
+            int n2 = 0;
+            int m = 0;
+
+            
+            //
+            // Enough place?
+            //
+            if( alglib.ap.cols(a)>=n && alglib.ap.rows(a)>=minrows )
+            {
+                return;
+            }
+            
+            //
+            // Sizes and metrics
+            //
+            if( alglib.ap.cols(a)<n )
+            {
+                n = Math.Max(n, (int)Math.Round(1.8*alglib.ap.cols(a)+1));
+            }
+            n2 = Math.Min(alglib.ap.cols(a), n);
+            m = alglib.ap.rows(a);
+            
+            //
+            // Grow
+            //
+            alglib.ap.swap(ref a, ref olda);
+            a = new double[Math.Max(m, minrows), n];
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n2-1; j++)
+                {
+                    a[i,j] = olda[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Grows X, i.e. changes its size in such a way that:
+        a) contents is preserved
+        b) new size is at least N
+        c) new size can be larger than N, so subsequent grow() calls can return
+           without reallocation
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rvectorgrowto(ref double[] x,
+            int n,
+            alglib.xparams _params)
+        {
+            double[] oldx = new double[0];
+            int i = 0;
+            int n2 = 0;
+
+            
+            //
+            // Enough place
+            //
+            if( alglib.ap.len(x)>=n )
+            {
+                return;
+            }
+            
+            //
+            // Choose new size
+            //
+            n = Math.Max(n, (int)Math.Round(1.8*alglib.ap.len(x)+1));
+            
+            //
+            // Grow
+            //
+            n2 = alglib.ap.len(x);
+            alglib.ap.swap(ref x, ref oldx);
+            x = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                if( i<n2 )
+                {
+                    x[i] = oldx[i];
+                }
+                else
+                {
+                    x[i] = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Resizes X and:
+        * preserves old contents of X
+        * fills new elements by zeros
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ivectorresize(ref int[] x,
+            int n,
+            alglib.xparams _params)
+        {
+            int[] oldx = new int[0];
+            int i = 0;
+            int n2 = 0;
+
+            n2 = alglib.ap.len(x);
+            alglib.ap.swap(ref x, ref oldx);
+            x = new int[n];
+            for(i=0; i<=n-1; i++)
+            {
+                if( i<n2 )
+                {
+                    x[i] = oldx[i];
+                }
+                else
+                {
+                    x[i] = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Resizes X and:
+        * preserves old contents of X
+        * fills new elements by zeros
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rvectorresize(ref double[] x,
+            int n,
+            alglib.xparams _params)
+        {
+            double[] oldx = new double[0];
+            int i = 0;
+            int n2 = 0;
+
+            n2 = alglib.ap.len(x);
+            alglib.ap.swap(ref x, ref oldx);
+            x = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                if( i<n2 )
+                {
+                    x[i] = oldx[i];
+                }
+                else
+                {
+                    x[i] = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Resizes X and:
+        * preserves old contents of X
+        * fills new elements by zeros
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void imatrixresize(ref int[,] x,
+            int m,
+            int n,
+            alglib.xparams _params)
+        {
+            int[,] oldx = new int[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 int[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;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        appends element to X
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ivectorappend(ref int[] x,
+            int v,
+            alglib.xparams _params)
+        {
+            int[] oldx = new int[0];
+            int i = 0;
+            int n = 0;
+
+            n = alglib.ap.len(x);
+            alglib.ap.swap(ref x, ref oldx);
+            x = new int[n+1];
+            for(i=0; i<=n-1; i++)
+            {
+                x[i] = oldx[i];
+            }
+            x[n] = v;
+        }
+
+
+        /*************************************************************************
+        This function checks that length(X) is at least N and first N values  from
+        X[] are finite
+
+          -- ALGLIB --
+             Copyright 18.06.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool isfinitevector(double[] x,
+            int n,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+            double v = 0;
+
+            alglib.ap.assert(n>=0, "APSERVIsFiniteVector: internal error (N<0)");
+            if( n==0 )
+            {
+                result = true;
+                return result;
+            }
+            if( alglib.ap.len(x)<n )
+            {
+                result = false;
+                return result;
+            }
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.01*v+x[i];
+            }
+            result = math.isfinite(v);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function checks that first N values from X[] are finite
+
+          -- ALGLIB --
+             Copyright 18.06.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool isfinitecvector(complex[] z,
+            int n,
+            alglib.xparams _params)
+        {
+            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 size of X is at least MxN and 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,
+            alglib.xparams _params)
+        {
+            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)");
+            if( m==0 || n==0 )
+            {
+                result = true;
+                return result;
+            }
+            if( alglib.ap.rows(x)<m || alglib.ap.cols(x)<n )
+            {
+                result = false;
+                return result;
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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 size of X is at least NxN and 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,
+            alglib.xparams _params)
+        {
+            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)");
+            if( n==0 )
+            {
+                result = true;
+                return result;
+            }
+            if( alglib.ap.rows(x)<n || alglib.ap.cols(x)<n )
+            {
+                result = false;
+                return result;
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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);
+        }
+
+
+        /*************************************************************************
+        Returns random normal number using low-quality system-provided generator
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double randomnormal(alglib.xparams _params)
+        {
+            double result = 0;
+            double u = 0;
+            double v = 0;
+            double s = 0;
+
+            while( true )
+            {
+                u = 2*math.randomreal()-1;
+                v = 2*math.randomreal()-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);
+                    result = u*s;
+                    break;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Generates random unit vector using low-quality system-provided generator.
+        Reallocates array if its size is too short.
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void randomunit(int n,
+            ref double[] x,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double v = 0;
+            double vv = 0;
+
+            alglib.ap.assert(n>0, "RandomUnit: N<=0");
+            if( alglib.ap.len(x)<n )
+            {
+                x = new double[n];
+            }
+            do
+            {
+                v = 0.0;
+                for(i=0; i<=n-1; i++)
+                {
+                    vv = randomnormal(_params);
+                    x[i] = vv;
+                    v = v+vv*vv;
+                }
+            }
+            while( (double)(v)<=(double)(0) );
+            v = 1/Math.Sqrt(v);
+            for(i=0; i<=n-1; i++)
+            {
+                x[i] = x[i]*v;
+            }
+        }
+
+
+        /*************************************************************************
+        This function is used to swap two integer values
+        *************************************************************************/
+        public static void swapi(ref int v0,
+            ref int v1,
+            alglib.xparams _params)
+        {
+            int v = 0;
+
+            v = v0;
+            v0 = v1;
+            v1 = v;
+        }
+
+
+        /*************************************************************************
+        This function is used to swap two real values
+        *************************************************************************/
+        public static void swapr(ref double v0,
+            ref double v1,
+            alglib.xparams _params)
+        {
+            double v = 0;
+
+            v = v0;
+            v0 = v1;
+            v1 = v;
+        }
+
+
+        /*************************************************************************
+        This function is used to swap two rows of the matrix; if NCols<0, automatically
+        determined from the matrix size.
+        *************************************************************************/
+        public static void swaprows(double[,] a,
+            int i0,
+            int i1,
+            int ncols,
+            alglib.xparams _params)
+        {
+            int j = 0;
+            double v = 0;
+
+            if( i0==i1 )
+            {
+                return;
+            }
+            if( ncols<0 )
+            {
+                ncols = alglib.ap.cols(a);
+            }
+            for(j=0; j<=ncols-1; j++)
+            {
+                v = a[i0,j];
+                a[i0,j] = a[i1,j];
+                a[i1,j] = v;
+            }
+        }
+
+
+        /*************************************************************************
+        This function is used to swap two "entries" in 1-dimensional array composed
+        from D-element entries
+        *************************************************************************/
+        public static void swapentries(double[] a,
+            int i0,
+            int i1,
+            int entrywidth,
+            alglib.xparams _params)
+        {
+            int offs0 = 0;
+            int offs1 = 0;
+            int j = 0;
+            double v = 0;
+
+            if( i0==i1 )
+            {
+                return;
+            }
+            offs0 = i0*entrywidth;
+            offs1 = i1*entrywidth;
+            for(j=0; j<=entrywidth-1; j++)
+            {
+                v = a[offs0+j];
+                a[offs0+j] = a[offs1+j];
+                a[offs1+j] = v;
+            }
+        }
+
+
+        /*************************************************************************
+        This function is used to swap two elements of the vector
+        *************************************************************************/
+        public static void swapelements(double[] a,
+            int i0,
+            int i1,
+            alglib.xparams _params)
+        {
+            double v = 0;
+
+            if( i0==i1 )
+            {
+                return;
+            }
+            v = a[i0];
+            a[i0] = a[i1];
+            a[i1] = v;
+        }
+
+
+        /*************************************************************************
+        This function is used to swap two elements of the vector
+        *************************************************************************/
+        public static void swapelementsi(int[] a,
+            int i0,
+            int i1,
+            alglib.xparams _params)
+        {
+            int v = 0;
+
+            if( i0==i1 )
+            {
+                return;
+            }
+            v = a[i0];
+            a[i0] = a[i1];
+            a[i1] = v;
+        }
+
+
+        /*************************************************************************
+        This function is used to return maximum of three real values
+        *************************************************************************/
+        public static double maxreal3(double v0,
+            double v1,
+            double v2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = v0;
+            if( (double)(result)<(double)(v1) )
+            {
+                result = v1;
+            }
+            if( (double)(result)<(double)(v2) )
+            {
+                result = v2;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function is used to increment value of integer variable
+        *************************************************************************/
+        public static void inc(ref int v,
+            alglib.xparams _params)
+        {
+            v = v+1;
+        }
+
+
+        /*************************************************************************
+        This function is used to decrement value of integer variable
+        *************************************************************************/
+        public static void dec(ref int v,
+            alglib.xparams _params)
+        {
+            v = v-1;
+        }
+
+
+        /*************************************************************************
+        This function is used to increment value of integer variable; name of  the
+        function suggests that increment is done in multithreaded setting  in  the
+        thread-unsafe manner (optional progress reports which do not need guaranteed
+        correctness)
+        *************************************************************************/
+        public static void threadunsafeinc(ref int v,
+            alglib.xparams _params)
+        {
+            v = v+1;
+        }
+
+
+        /*************************************************************************
+        This function performs two operations:
+        1. decrements value of integer variable, if it is positive
+        2. explicitly sets variable to zero if it is non-positive
+        It is used by some algorithms to decrease value of internal counters.
+        *************************************************************************/
+        public static void countdown(ref int v,
+            alglib.xparams _params)
+        {
+            if( v>0 )
+            {
+                v = v-1;
+            }
+            else
+            {
+                v = 0;
+            }
+        }
+
+
+        /*************************************************************************
+        This function returns +1 or -1 depending on sign of X.
+        x=0 results in +1 being returned.
+        *************************************************************************/
+        public static double possign(double x,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            if( (double)(x)>=(double)(0) )
+            {
+                result = 1;
+            }
+            else
+            {
+                result = -1;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns product of two real numbers. It is convenient when
+        you have to perform typecast-and-product of two INTEGERS.
+        *************************************************************************/
+        public static double rmul2(double v0,
+            double v1,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = v0*v1;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns product of three real numbers. It is convenient when
+        you have to perform typecast-and-product of two INTEGERS.
+        *************************************************************************/
+        public static double rmul3(double v0,
+            double v1,
+            double v2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = v0*v1*v2;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns (A div B) rounded up; it expects that A>0, B>0, but
+        does not check it.
+        *************************************************************************/
+        public static int idivup(int a,
+            int b,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = a/b;
+            if( a%b>0 )
+            {
+                result = result+1;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns min(i0,i1)
+        *************************************************************************/
+        public static int imin2(int i0,
+            int i1,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = i0;
+            if( i1<result )
+            {
+                result = i1;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns min(i0,i1,i2)
+        *************************************************************************/
+        public static int imin3(int i0,
+            int i1,
+            int i2,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = i0;
+            if( i1<result )
+            {
+                result = i1;
+            }
+            if( i2<result )
+            {
+                result = i2;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns max(i0,i1)
+        *************************************************************************/
+        public static int imax2(int i0,
+            int i1,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = i0;
+            if( i1>result )
+            {
+                result = i1;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns max(i0,i1,i2)
+        *************************************************************************/
+        public static int imax3(int i0,
+            int i1,
+            int i2,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = i0;
+            if( i1>result )
+            {
+                result = i1;
+            }
+            if( i2>result )
+            {
+                result = i2;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns max(r0,r1,r2)
+        *************************************************************************/
+        public static double rmax3(double r0,
+            double r1,
+            double r2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = r0;
+            if( (double)(r1)>(double)(result) )
+            {
+                result = r1;
+            }
+            if( (double)(r2)>(double)(result) )
+            {
+                result = r2;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns max(|r0|,|r1|,|r2|)
+        *************************************************************************/
+        public static double rmaxabs3(double r0,
+            double r1,
+            double r2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            r0 = Math.Abs(r0);
+            r1 = Math.Abs(r1);
+            r2 = Math.Abs(r2);
+            result = r0;
+            if( (double)(r1)>(double)(result) )
+            {
+                result = r1;
+            }
+            if( (double)(r2)>(double)(result) )
+            {
+                result = r2;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        '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,
+            alglib.xparams _params)
+        {
+            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;
+        }
+
+
+        /*************************************************************************
+        'bounds' value: maps X to [B1,B2]
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static int iboundval(int x,
+            int b1,
+            int b2,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            if( x<=b1 )
+            {
+                result = b1;
+                return result;
+            }
+            if( x>=b2 )
+            {
+                result = b2;
+                return result;
+            }
+            result = x;
+            return result;
+        }
+
+
+        /*************************************************************************
+        'bounds' value: maps X to [B1,B2]
+
+          -- ALGLIB --
+             Copyright 20.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double rboundval(double x,
+            double b1,
+            double b2,
+            alglib.xparams _params)
+        {
+            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;
+        }
+
+
+        /*************************************************************************
+        Returns number of non-zeros
+        *************************************************************************/
+        public static int countnz1(double[] v,
+            int n,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+
+            result = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( !(v[i]==0) )
+                {
+                    result = result+1;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns number of non-zeros
+        *************************************************************************/
+        public static int countnz2(double[,] v,
+            int m,
+            int n,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+            int j = 0;
+
+            result = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( !(v[i,j]==0) )
+                    {
+                        result = result+1;
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Allocation of serializer: complex value
+        *************************************************************************/
+        public static void alloccomplex(alglib.serializer s,
+            complex v,
+            alglib.xparams _params)
+        {
+            s.alloc_entry();
+            s.alloc_entry();
+        }
+
+
+        /*************************************************************************
+        Serialization: complex value
+        *************************************************************************/
+        public static void serializecomplex(alglib.serializer s,
+            complex v,
+            alglib.xparams _params)
+        {
+            s.serialize_double(v.x);
+            s.serialize_double(v.y);
+        }
+
+
+        /*************************************************************************
+        Unserialization: complex value
+        *************************************************************************/
+        public static complex unserializecomplex(alglib.serializer s,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 boolean array
+        *************************************************************************/
+        public static void copybooleanarray(bool[] src,
+            ref bool[] dst,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            dst = new bool[0];
+
+            if( alglib.ap.len(src)>0 )
+            {
+                dst = new bool[alglib.ap.len(src)];
+                for(i=0; i<=alglib.ap.len(src)-1; i++)
+                {
+                    dst[i] = src[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Copy integer array
+        *************************************************************************/
+        public static void copyintegerarray(int[] src,
+            ref int[] dst,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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];
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Clears integer array
+        *************************************************************************/
+        public static void unsetintegerarray(ref int[] a,
+            alglib.xparams _params)
+        {
+            a = new int[0];
+
+        }
+
+
+        /*************************************************************************
+        Clears real array
+        *************************************************************************/
+        public static void unsetrealarray(ref double[] a,
+            alglib.xparams _params)
+        {
+            a = new double[0];
+
+        }
+
+
+        /*************************************************************************
+        Clears real matrix
+        *************************************************************************/
+        public static void unsetrealmatrix(ref double[,] a,
+            alglib.xparams _params)
+        {
+            a = new double[0,0];
+
+        }
+
+
+        /*************************************************************************
+        This function is used in parallel functions for recurrent division of large
+        task into two smaller tasks.
+
+        It has following properties:
+        * it works only for TaskSize>=2 and TaskSize>TileSize (assertion is thrown otherwise)
+        * Task0+Task1=TaskSize, Task0>0, Task1>0
+        * Task0 and Task1 are close to each other
+        * Task0>=Task1
+        * Task0 is always divisible by TileSize
+
+          -- ALGLIB --
+             Copyright 07.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void tiledsplit(int tasksize,
+            int tilesize,
+            ref int task0,
+            ref int task1,
+            alglib.xparams _params)
+        {
+            int cc = 0;
+
+            task0 = 0;
+            task1 = 0;
+
+            alglib.ap.assert(tasksize>=2, "TiledSplit: TaskSize<2");
+            alglib.ap.assert(tasksize>tilesize, "TiledSplit: TaskSize<=TileSize");
+            cc = chunkscount(tasksize, tilesize, _params);
+            alglib.ap.assert(cc>=2, "TiledSplit: integrity check failed");
+            task0 = idivup(cc, 2, _params)*tilesize;
+            task1 = tasksize-task0;
+            alglib.ap.assert(task0>=1, "TiledSplit: internal error");
+            alglib.ap.assert(task1>=1, "TiledSplit: internal error");
+            alglib.ap.assert(task0%tilesize==0, "TiledSplit: internal error");
+            alglib.ap.assert(task0>=task1, "TiledSplit: internal error");
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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;
+        }
+
+
+        /*************************************************************************
+        This function is used in parallel functions for recurrent division of large
+        task into two smaller tasks.
+
+        It has following properties:
+        * it works only for TaskSize>=2 (assertion is thrown otherwise)
+        * for TaskSize=2, it returns Task0=1, Task1=1
+        * in case TaskSize is odd,  Task0=TaskSize-1, Task1=1
+        * in case TaskSize is even, Task0 and Task1 are approximately TaskSize/2
+          and both Task0 and Task1 are even, Task0>=Task1
+
+          -- ALGLIB --
+             Copyright 07.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void splitlengtheven(int tasksize,
+            ref int task0,
+            ref int task1,
+            alglib.xparams _params)
+        {
+            task0 = 0;
+            task1 = 0;
+
+            alglib.ap.assert(tasksize>=2, "SplitLengthEven: TaskSize<2");
+            if( tasksize==2 )
+            {
+                task0 = 1;
+                task1 = 1;
+                return;
+            }
+            if( tasksize%2==0 )
+            {
+                
+                //
+                // Even division
+                //
+                task0 = tasksize/2;
+                task1 = tasksize/2;
+                if( task0%2!=0 )
+                {
+                    task0 = task0+1;
+                    task1 = task1-1;
+                }
+            }
+            else
+            {
+                
+                //
+                // Odd task size, split trailing odd part from it.
+                //
+                task0 = tasksize-1;
+                task1 = 1;
+            }
+            alglib.ap.assert(task0>=1, "SplitLengthEven: internal error");
+            alglib.ap.assert(task1>=1, "SplitLengthEven: internal error");
+        }
+
+
+        /*************************************************************************
+        This function is used to calculate number of chunks (including partial,
+        non-complete chunks) in some set. It expects that ChunkSize>=1, TaskSize>=0.
+        Assertion is thrown otherwise.
+
+        Function result is equivalent to Ceil(TaskSize/ChunkSize), but with guarantees
+        that rounding errors won't ruin results.
+
+          -- ALGLIB --
+             Copyright 21.01.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static int chunkscount(int tasksize,
+            int chunksize,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            alglib.ap.assert(tasksize>=0, "ChunksCount: TaskSize<0");
+            alglib.ap.assert(chunksize>=1, "ChunksCount: ChunkSize<1");
+            result = tasksize/chunksize;
+            if( tasksize%chunksize!=0 )
+            {
+                result = result+1;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns maximum density for level 2 sparse/dense functions. Density values
+        below one returned by this function are better to handle via sparse Level 2
+        functionality.
+
+          -- ALGLIB routine --
+             10.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        public static double sparselevel2density(alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = 0.1;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns A-tile size for a matrix.
+
+        A-tiles are smallest tiles (32x32), suitable for processing by ALGLIB  own
+        implementation of Level 3 linear algebra.
+
+          -- ALGLIB routine --
+             10.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        public static int matrixtilesizea(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 32;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns B-tile size for a matrix.
+
+        B-tiles are larger  tiles (64x64), suitable for parallel execution or for
+        processing by vendor's implementation of Level 3 linear algebra.
+
+          -- ALGLIB routine --
+             10.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        public static int matrixtilesizeb(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 64;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns minimum cost of task which is feasible for
+        multithreaded processing. It returns real number in order to avoid overflow
+        problems.
+
+          -- ALGLIB --
+             Copyright 10.01.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static double smpactivationlevel(alglib.xparams _params)
+        {
+            double result = 0;
+            double nn = 0;
+
+            nn = 2*matrixtilesizeb(_params);
+            result = Math.Max(0.95*2*nn*nn*nn, 1.0E7);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns minimum cost of task which is feasible for
+        spawn (given that multithreading is active).
+
+        It returns real number in order to avoid overflow problems.
+
+          -- ALGLIB --
+             Copyright 10.01.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static double spawnlevel(alglib.xparams _params)
+        {
+            double result = 0;
+            double nn = 0;
+
+            nn = 2*matrixtilesizea(_params);
+            result = 0.95*2*nn*nn*nn;
+            return result;
+        }
+
+
+        /*************************************************************************
+        --- OBSOLETE FUNCTION, USE TILED SPLIT INSTEAD --- 
+
+        This function is used in parallel functions for recurrent division of large
+        task into two smaller tasks.
+
+        It has following properties:
+        * it works only for TaskSize>=2 and ChunkSize>=2
+          (assertion is thrown otherwise)
+        * Task0+Task1=TaskSize, Task0>0, Task1>0
+        * Task0 and Task1 are close to each other
+        * in case TaskSize>ChunkSize, Task0 is always divisible by ChunkSize
+
+          -- ALGLIB --
+             Copyright 07.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void splitlength(int tasksize,
+            int chunksize,
+            ref int task0,
+            ref int task1,
+            alglib.xparams _params)
+        {
+            task0 = 0;
+            task1 = 0;
+
+            alglib.ap.assert(chunksize>=2, "SplitLength: ChunkSize<2");
+            alglib.ap.assert(tasksize>=2, "SplitLength: TaskSize<2");
+            task0 = tasksize/2;
+            if( task0>chunksize && task0%chunksize!=0 )
+            {
+                task0 = task0-task0%chunksize;
+            }
+            task1 = tasksize-task0;
+            alglib.ap.assert(task0>=1, "SplitLength: internal error");
+            alglib.ap.assert(task1>=1, "SplitLength: internal error");
+        }
+
+
+    }
+    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,
+            alglib.xparams _params)
+        {
+            apserv.apbuffers buf = new apserv.apbuffers();
+
+            p1 = new int[0];
+            p2 = new int[0];
+
+            tagsortbuf(ref a, n, ref p1, ref p2, buf, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                apserv.ivectorsetlengthatleast(ref p2, 1, _params);
+                p1[0] = 0;
+                p2[0] = 0;
+                return;
+            }
+            
+            //
+            // General case, N>1: prepare permutations table P1
+            //
+            apserv.ivectorsetlengthatleast(ref p1, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                p1[i] = i;
+            }
+            
+            //
+            // General case, N>1: sort, update P1
+            //
+            apserv.rvectorsetlengthatleast(ref buf.ra0, n, _params);
+            apserv.ivectorsetlengthatleast(ref buf.ia0, n, _params);
+            tagsortfasti(ref a, ref p1, ref buf.ra0, ref buf.ia0, n, _params);
+            
+            //
+            // 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, _params);
+            apserv.ivectorsetlengthatleast(ref buf.ia1, n, _params);
+            apserv.ivectorsetlengthatleast(ref p2, n, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+            int t = 0;
+            int tmp = 0;
+            double tmpr = 0;
+            int p0 = 0;
+            int p1 = 0;
+            int at = 0;
+            int ak = 0;
+            int ak1 = 0;
+            double bt = 0;
+
+            
+            //
+            // Special cases
+            //
+            if( n<=1 )
+            {
+                return;
+            }
+            
+            //
+            // General case, N>1: sort, update B
+            //
+            for(i=2; i<=n; i++)
+            {
+                t = i;
+                while( t!=1 )
+                {
+                    k = t/2;
+                    p0 = offset+k-1;
+                    p1 = offset+t-1;
+                    ak = a[p0];
+                    at = a[p1];
+                    if( ak>=at )
+                    {
+                        break;
+                    }
+                    a[p0] = at;
+                    a[p1] = ak;
+                    tmpr = b[p0];
+                    b[p0] = b[p1];
+                    b[p1] = tmpr;
+                    t = k;
+                }
+            }
+            for(i=n-1; i>=1; i--)
+            {
+                p0 = offset+0;
+                p1 = offset+i;
+                tmp = a[p1];
+                a[p1] = a[p0];
+                a[p0] = tmp;
+                at = tmp;
+                tmpr = b[p1];
+                b[p1] = b[p0];
+                b[p0] = tmpr;
+                bt = tmpr;
+                t = 0;
+                while( true )
+                {
+                    k = 2*t+1;
+                    if( k+1>i )
+                    {
+                        break;
+                    }
+                    p0 = offset+t;
+                    p1 = offset+k;
+                    ak = a[p1];
+                    if( k+1<i )
+                    {
+                        ak1 = a[p1+1];
+                        if( ak1>ak )
+                        {
+                            ak = ak1;
+                            p1 = p1+1;
+                            k = k+1;
+                        }
+                    }
+                    if( at>=ak )
+                    {
+                        break;
+                    }
+                    a[p1] = at;
+                    a[p0] = ak;
+                    b[p0] = b[p1];
+                    b[p1] = bt;
+                    t = k;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Sorting function optimized for integer values (only keys, no labels),  can
+        be used to sort middle of the array
+
+          -- ALGLIB --
+             Copyright 11.12.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sortmiddlei(int[] a,
+            int offset,
+            int n,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+            int t = 0;
+            int tmp = 0;
+            int p0 = 0;
+            int p1 = 0;
+            int at = 0;
+            int ak = 0;
+            int ak1 = 0;
+
+            
+            //
+            // Special cases
+            //
+            if( n<=1 )
+            {
+                return;
+            }
+            
+            //
+            // General case, N>1: sort, update B
+            //
+            for(i=2; i<=n; i++)
+            {
+                t = i;
+                while( t!=1 )
+                {
+                    k = t/2;
+                    p0 = offset+k-1;
+                    p1 = offset+t-1;
+                    ak = a[p0];
+                    at = a[p1];
+                    if( ak>=at )
+                    {
+                        break;
+                    }
+                    a[p0] = at;
+                    a[p1] = ak;
+                    t = k;
+                }
+            }
+            for(i=n-1; i>=1; i--)
+            {
+                p0 = offset+0;
+                p1 = offset+i;
+                tmp = a[p1];
+                a[p1] = a[p0];
+                a[p0] = tmp;
+                at = tmp;
+                t = 0;
+                while( true )
+                {
+                    k = 2*t+1;
+                    if( k+1>i )
+                    {
+                        break;
+                    }
+                    p0 = offset+t;
+                    p1 = offset+k;
+                    ak = a[p1];
+                    if( k+1<i )
+                    {
+                        ak1 = a[p1+1];
+                        if( ak1>ak )
+                        {
+                            ak = ak1;
+                            p1 = p1+1;
+                            k = k+1;
+                        }
+                    }
+                    if( at>=ak )
+                    {
+                        break;
+                    }
+                    a[p1] = at;
+                    a[p0] = ak;
+                    t = k;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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( v<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,
+            alglib.xparams _params)
+        {
+            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( v>va )
+                    {
+                        a[j] = v;
+                        b[j] = b[k1];
+                        j = k1;
+                    }
+                    break;
+                }
+                else
+                {
+                    
+                    //
+                    // two childs
+                    //
+                    v1 = a[k1];
+                    v2 = a[k2];
+                    if( v1>v2 )
+                    {
+                        if( va<v1 )
+                        {
+                            a[j] = v1;
+                            b[j] = b[k1];
+                            j = k1;
+                        }
+                        else
+                        {
+                            break;
+                        }
+                    }
+                    else
+                    {
+                        if( va<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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            tagsortfastirec(ref a, ref b, ref bufa, ref bufb, i1+cntless+cnteq, i2, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            tagsortfastrrec(ref a, ref b, ref bufa, ref bufb, i1+cntless+cnteq, i2, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            tagsortfastrec(ref a, ref bufa, i1+cntless+cnteq, i2, _params);
+        }
+
+
+    }
+    public class ablasmkl
+    {
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             12.10.2017
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixgermkl(int m,
+            int n,
+            double[,] a,
+            int ia,
+            int ja,
+            double alpha,
+            double[] u,
+            int iu,
+            double[] v,
+            int iv,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             12.10.2017
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixrank1mkl(int m,
+            int n,
+            ref complex[,] a,
+            int ia,
+            int ja,
+            ref complex[] u,
+            int iu,
+            ref complex[] v,
+            int iv,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             12.10.2017
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixrank1mkl(int m,
+            int n,
+            double[,] a,
+            int ia,
+            int ja,
+            double[] u,
+            int iu,
+            double[] v,
+            int iv,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             12.10.2017
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixmvmkl(int m,
+            int n,
+            complex[,] a,
+            int ia,
+            int ja,
+            int opa,
+            complex[] x,
+            int ix,
+            ref complex[] y,
+            int iy,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             12.10.2017
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixmvmkl(int m,
+            int n,
+            double[,] a,
+            int ia,
+            int ja,
+            int opa,
+            double[] x,
+            int ix,
+            double[] y,
+            int iy,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             12.10.2017
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixgemvmkl(int m,
+            int n,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            int opa,
+            double[] x,
+            int ix,
+            double beta,
+            double[] y,
+            int iy,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             12.10.2017
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixtrsvmkl(int n,
+            double[,] a,
+            int ia,
+            int ja,
+            bool isupper,
+            bool isunit,
+            int optype,
+            double[] x,
+            int ix,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             01.10.2013
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixsyrkmkl(int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            double[,] c,
+            int ic,
+            int jc,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             01.10.2013
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixherkmkl(int n,
+            int k,
+            double alpha,
+            complex[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            complex[,] c,
+            int ic,
+            int jc,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             01.10.2013
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixgemmmkl(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,
+            double[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             01.10.2017
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixsymvmkl(int n,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            bool isupper,
+            double[] x,
+            int ix,
+            double beta,
+            double[] y,
+            int iy,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             16.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixgemmmkl(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,
+            complex[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             16.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixlefttrsmmkl(int m,
+            int n,
+            complex[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            complex[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             16.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixrighttrsmmkl(int m,
+            int n,
+            complex[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            complex[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             16.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixlefttrsmmkl(int m,
+            int n,
+            double[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            double[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel
+
+          -- ALGLIB routine --
+             16.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixrighttrsmmkl(int m,
+            int n,
+            double[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            double[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+        NOTE:
+
+        if function returned False, CholResult is NOT modified. Not ever referenced!
+        if function returned True, CholResult is set to status of Cholesky decomposition
+        (True on succeess).
+
+          -- ALGLIB routine --
+             16.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool spdmatrixcholeskymkl(double[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            ref bool cholresult,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixplumkl(ref double[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+        NOTE: this function needs preallocated output/temporary arrays.
+              D and E must be at least max(M,N)-wide.
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixbdmkl(double[,] a,
+            int m,
+            int n,
+            double[] d,
+            double[] e,
+            double[] tauq,
+            double[] taup,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+        If ByQ is True,  TauP is not used (can be empty array).
+        If ByQ is False, TauQ is not used (can be empty array).
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixbdmultiplybymkl(double[,] qp,
+            int m,
+            int n,
+            double[] tauq,
+            double[] taup,
+            double[,] z,
+            int zrows,
+            int zcolumns,
+            bool byq,
+            bool fromtheright,
+            bool dotranspose,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+        NOTE: Tau must be preallocated array with at least N-1 elements.
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixhessenbergmkl(double[,] a,
+            int n,
+            double[] tau,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+        NOTE: Q must be preallocated N*N array
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixhessenbergunpackqmkl(double[,] a,
+            int n,
+            double[] tau,
+            double[,] q,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+        NOTE: Tau, D, E must be preallocated arrays;
+              length(E)=length(Tau)=N-1 (or larger)
+              length(D)=N (or larger)
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool smatrixtdmkl(double[,] a,
+            int n,
+            bool isupper,
+            double[] tau,
+            double[] d,
+            double[] e,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+        NOTE: Q must be preallocated N*N array
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool smatrixtdunpackqmkl(double[,] a,
+            int n,
+            bool isupper,
+            double[] tau,
+            double[,] q,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+        NOTE: Tau, D, E must be preallocated arrays;
+              length(E)=length(Tau)=N-1 (or larger)
+              length(D)=N (or larger)
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool hmatrixtdmkl(complex[,] a,
+            int n,
+            bool isupper,
+            complex[] tau,
+            double[] d,
+            double[] e,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+        NOTE: Q must be preallocated N*N array
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool hmatrixtdunpackqmkl(complex[,] a,
+            int n,
+            bool isupper,
+            complex[] tau,
+            complex[,] q,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+        Returns True if MKL was present and handled request (MKL  completion  code
+        is returned as separate output parameter).
+
+        D and E are pre-allocated arrays with length N (both of them!). On output,
+        D constraints singular values, and E is destroyed.
+
+        SVDResult is modified if and only if MKL is present.
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixbdsvdmkl(double[] d,
+            double[] e,
+            int n,
+            bool isupper,
+            double[,] u,
+            int nru,
+            double[,] c,
+            int ncc,
+            double[,] vt,
+            int ncvt,
+            ref bool svdresult,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based DHSEQR kernel.
+
+        Returns True if MKL was present and handled request.
+
+        WR and WI are pre-allocated arrays with length N.
+        Z is pre-allocated array[N,N].
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixinternalschurdecompositionmkl(double[,] h,
+            int n,
+            int tneeded,
+            int zneeded,
+            double[] wr,
+            double[] wi,
+            double[,] z,
+            ref int info,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based DTREVC kernel.
+
+        Returns True if MKL was present and handled request.
+
+        NOTE: this function does NOT support HOWMNY=3!!!!
+
+        VL and VR are pre-allocated arrays with length N*N, if required. If particalar
+        variables is not required, it can be dummy (empty) array.
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixinternaltrevcmkl(double[,] t,
+            int n,
+            int side,
+            int howmny,
+            double[,] vl,
+            double[,] vr,
+            ref int m,
+            ref int info,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+        Returns True if MKL was present and handled request (MKL  completion  code
+        is returned as separate output parameter).
+
+        D and E are pre-allocated arrays with length N (both of them!). On output,
+        D constraints eigenvalues, and E is destroyed.
+
+        Z is preallocated array[N,N] for ZNeeded<>0; ignored for ZNeeded=0.
+
+        EVDResult is modified if and only if MKL is present.
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool smatrixtdevdmkl(double[] d,
+            double[] e,
+            int n,
+            int zneeded,
+            double[,] z,
+            ref bool evdresult,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MKL-based kernel.
+
+        Returns True if MKL was present and handled request (MKL  completion  code
+        is returned as separate output parameter).
+
+        D and E are pre-allocated arrays with length N (both of them!). On output,
+        D constraints eigenvalues, and E is destroyed.
+
+        Z is preallocated array[N,N] for ZNeeded<>0; ignored for ZNeeded=0.
+
+        EVDResult is modified if and only if MKL is present.
+
+          -- ALGLIB routine --
+             20.10.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool sparsegemvcrsmkl(int opa,
+            int arows,
+            int acols,
+            double alpha,
+            double[] vals,
+            int[] cidx,
+            int[] ridx,
+            double[] x,
+            int ix,
+            double beta,
+            double[] y,
+            int iy,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+    }
+    public class ablasf
+    {
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool rmatrixgerf(int m,
+            int n,
+            double[,] a,
+            int ia,
+            int ja,
+            double ralpha,
+            double[] u,
+            int iu,
+            double[] v,
+            int iv,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            complex[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            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,
+            complex[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            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,
+            double[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            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,
+            double[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel
+
+          -- ALGLIB routine --
+             19.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool cmatrixherkf(int n,
+            int k,
+            double alpha,
+            complex[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            complex[,] c,
+            int ic,
+            int jc,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            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,
+            double[,] c,
+            int ic,
+            int jc,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            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,
+            double[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            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,
+            complex[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        CMatrixGEMM kernel, basecase code for CMatrixGEMM.
+
+        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:
+        * 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.
+
+        IMPORTANT:
+
+        This function does NOT preallocate output matrix C, it MUST be preallocated
+        by caller prior to calling this function. In case C does not have  enough
+        space to store result, exception will be generated.
+
+        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       -   PREALLOCATED output matrix
+            IC      -   submatrix offset
+            JC      -   submatrix offset
+
+          -- ALGLIB routine --
+             27.03.2013
+             Bochkanov Sergey
+        *************************************************************************/
+        public 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,
+            complex[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            complex v = 0;
+            complex v00 = 0;
+            complex v01 = 0;
+            complex v10 = 0;
+            complex v11 = 0;
+            double v00x = 0;
+            double v00y = 0;
+            double v01x = 0;
+            double v01y = 0;
+            double v10x = 0;
+            double v10y = 0;
+            double v11x = 0;
+            double v11y = 0;
+            double a0x = 0;
+            double a0y = 0;
+            double a1x = 0;
+            double a1y = 0;
+            double b0x = 0;
+            double b0y = 0;
+            double b1x = 0;
+            double b1y = 0;
+            int idxa0 = 0;
+            int idxa1 = 0;
+            int idxb0 = 0;
+            int idxb1 = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int ik = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int jk = 0;
+            int t = 0;
+            int offsa = 0;
+            int offsb = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // if matrix size is zero
+            //
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            
+            //
+            // Try optimized code
+            //
+            if( cmatrixgemmf(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params) )
+            {
+                return;
+            }
+            
+            //
+            // if K=0 or Alpha=0, then C=Beta*C
+            //
+            if( k==0 || alpha==0 )
+            {
+                if( beta!=1 )
+                {
+                    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;
+            }
+            
+            //
+            // This phase is not really necessary, but compiler complains
+            // about "possibly uninitialized variables"
+            //
+            a0x = 0;
+            a0y = 0;
+            a1x = 0;
+            a1y = 0;
+            b0x = 0;
+            b0y = 0;
+            b1x = 0;
+            b1y = 0;
+            
+            //
+            // General case
+            //
+            i = 0;
+            while( i<m )
+            {
+                j = 0;
+                while( j<n )
+                {
+                    
+                    //
+                    // Choose between specialized 4x4 code and general code
+                    //
+                    if( i+2<=m && j+2<=n )
+                    {
+                        
+                        //
+                        // Specialized 4x4 code for [I..I+3]x[J..J+3] submatrix of C.
+                        //
+                        // This submatrix is calculated as sum of K rank-1 products,
+                        // with operands cached in local variables in order to speed
+                        // up operations with arrays.
+                        //
+                        v00x = 0.0;
+                        v00y = 0.0;
+                        v01x = 0.0;
+                        v01y = 0.0;
+                        v10x = 0.0;
+                        v10y = 0.0;
+                        v11x = 0.0;
+                        v11y = 0.0;
+                        if( optypea==0 )
+                        {
+                            idxa0 = ia+i+0;
+                            idxa1 = ia+i+1;
+                            offsa = ja;
+                        }
+                        else
+                        {
+                            idxa0 = ja+i+0;
+                            idxa1 = ja+i+1;
+                            offsa = ia;
+                        }
+                        if( optypeb==0 )
+                        {
+                            idxb0 = jb+j+0;
+                            idxb1 = jb+j+1;
+                            offsb = ib;
+                        }
+                        else
+                        {
+                            idxb0 = ib+j+0;
+                            idxb1 = ib+j+1;
+                            offsb = jb;
+                        }
+                        for(t=0; t<=k-1; t++)
+                        {
+                            if( optypea==0 )
+                            {
+                                a0x = a[idxa0,offsa].x;
+                                a0y = a[idxa0,offsa].y;
+                                a1x = a[idxa1,offsa].x;
+                                a1y = a[idxa1,offsa].y;
+                            }
+                            if( optypea==1 )
+                            {
+                                a0x = a[offsa,idxa0].x;
+                                a0y = a[offsa,idxa0].y;
+                                a1x = a[offsa,idxa1].x;
+                                a1y = a[offsa,idxa1].y;
+                            }
+                            if( optypea==2 )
+                            {
+                                a0x = a[offsa,idxa0].x;
+                                a0y = -a[offsa,idxa0].y;
+                                a1x = a[offsa,idxa1].x;
+                                a1y = -a[offsa,idxa1].y;
+                            }
+                            if( optypeb==0 )
+                            {
+                                b0x = b[offsb,idxb0].x;
+                                b0y = b[offsb,idxb0].y;
+                                b1x = b[offsb,idxb1].x;
+                                b1y = b[offsb,idxb1].y;
+                            }
+                            if( optypeb==1 )
+                            {
+                                b0x = b[idxb0,offsb].x;
+                                b0y = b[idxb0,offsb].y;
+                                b1x = b[idxb1,offsb].x;
+                                b1y = b[idxb1,offsb].y;
+                            }
+                            if( optypeb==2 )
+                            {
+                                b0x = b[idxb0,offsb].x;
+                                b0y = -b[idxb0,offsb].y;
+                                b1x = b[idxb1,offsb].x;
+                                b1y = -b[idxb1,offsb].y;
+                            }
+                            v00x = v00x+a0x*b0x-a0y*b0y;
+                            v00y = v00y+a0x*b0y+a0y*b0x;
+                            v01x = v01x+a0x*b1x-a0y*b1y;
+                            v01y = v01y+a0x*b1y+a0y*b1x;
+                            v10x = v10x+a1x*b0x-a1y*b0y;
+                            v10y = v10y+a1x*b0y+a1y*b0x;
+                            v11x = v11x+a1x*b1x-a1y*b1y;
+                            v11y = v11y+a1x*b1y+a1y*b1x;
+                            offsa = offsa+1;
+                            offsb = offsb+1;
+                        }
+                        v00.x = v00x;
+                        v00.y = v00y;
+                        v10.x = v10x;
+                        v10.y = v10y;
+                        v01.x = v01x;
+                        v01.y = v01y;
+                        v11.x = v11x;
+                        v11.y = v11y;
+                        if( beta==0 )
+                        {
+                            c[ic+i+0,jc+j+0] = alpha*v00;
+                            c[ic+i+0,jc+j+1] = alpha*v01;
+                            c[ic+i+1,jc+j+0] = alpha*v10;
+                            c[ic+i+1,jc+j+1] = alpha*v11;
+                        }
+                        else
+                        {
+                            c[ic+i+0,jc+j+0] = beta*c[ic+i+0,jc+j+0]+alpha*v00;
+                            c[ic+i+0,jc+j+1] = beta*c[ic+i+0,jc+j+1]+alpha*v01;
+                            c[ic+i+1,jc+j+0] = beta*c[ic+i+1,jc+j+0]+alpha*v10;
+                            c[ic+i+1,jc+j+1] = beta*c[ic+i+1,jc+j+1]+alpha*v11;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Determine submatrix [I0..I1]x[J0..J1] to process
+                        //
+                        i0 = i;
+                        i1 = Math.Min(i+1, m-1);
+                        j0 = j;
+                        j1 = Math.Min(j+1, n-1);
+                        
+                        //
+                        // Process submatrix
+                        //
+                        for(ik=i0; ik<=i1; ik++)
+                        {
+                            for(jk=j0; jk<=j1; jk++)
+                            {
+                                if( k==0 || alpha==0 )
+                                {
+                                    v = 0;
+                                }
+                                else
+                                {
+                                    v = 0.0;
+                                    if( optypea==0 && optypeb==0 )
+                                    {
+                                        i1_ = (ib)-(ja);
+                                        v = 0.0;
+                                        for(i_=ja; i_<=ja+k-1;i_++)
+                                        {
+                                            v += a[ia+ik,i_]*b[i_+i1_,jb+jk];
+                                        }
+                                    }
+                                    if( optypea==0 && optypeb==1 )
+                                    {
+                                        i1_ = (jb)-(ja);
+                                        v = 0.0;
+                                        for(i_=ja; i_<=ja+k-1;i_++)
+                                        {
+                                            v += a[ia+ik,i_]*b[ib+jk,i_+i1_];
+                                        }
+                                    }
+                                    if( optypea==0 && optypeb==2 )
+                                    {
+                                        i1_ = (jb)-(ja);
+                                        v = 0.0;
+                                        for(i_=ja; i_<=ja+k-1;i_++)
+                                        {
+                                            v += a[ia+ik,i_]*math.conj(b[ib+jk,i_+i1_]);
+                                        }
+                                    }
+                                    if( optypea==1 && optypeb==0 )
+                                    {
+                                        i1_ = (ib)-(ia);
+                                        v = 0.0;
+                                        for(i_=ia; i_<=ia+k-1;i_++)
+                                        {
+                                            v += a[i_,ja+ik]*b[i_+i1_,jb+jk];
+                                        }
+                                    }
+                                    if( optypea==1 && optypeb==1 )
+                                    {
+                                        i1_ = (jb)-(ia);
+                                        v = 0.0;
+                                        for(i_=ia; i_<=ia+k-1;i_++)
+                                        {
+                                            v += a[i_,ja+ik]*b[ib+jk,i_+i1_];
+                                        }
+                                    }
+                                    if( optypea==1 && optypeb==2 )
+                                    {
+                                        i1_ = (jb)-(ia);
+                                        v = 0.0;
+                                        for(i_=ia; i_<=ia+k-1;i_++)
+                                        {
+                                            v += a[i_,ja+ik]*math.conj(b[ib+jk,i_+i1_]);
+                                        }
+                                    }
+                                    if( optypea==2 && optypeb==0 )
+                                    {
+                                        i1_ = (ib)-(ia);
+                                        v = 0.0;
+                                        for(i_=ia; i_<=ia+k-1;i_++)
+                                        {
+                                            v += math.conj(a[i_,ja+ik])*b[i_+i1_,jb+jk];
+                                        }
+                                    }
+                                    if( optypea==2 && optypeb==1 )
+                                    {
+                                        i1_ = (jb)-(ia);
+                                        v = 0.0;
+                                        for(i_=ia; i_<=ia+k-1;i_++)
+                                        {
+                                            v += math.conj(a[i_,ja+ik])*b[ib+jk,i_+i1_];
+                                        }
+                                    }
+                                    if( optypea==2 && optypeb==2 )
+                                    {
+                                        i1_ = (jb)-(ia);
+                                        v = 0.0;
+                                        for(i_=ia; i_<=ia+k-1;i_++)
+                                        {
+                                            v += math.conj(a[i_,ja+ik])*math.conj(b[ib+jk,i_+i1_]);
+                                        }
+                                    }
+                                }
+                                if( beta==0 )
+                                {
+                                    c[ic+ik,jc+jk] = alpha*v;
+                                }
+                                else
+                                {
+                                    c[ic+ik,jc+jk] = beta*c[ic+ik,jc+jk]+alpha*v;
+                                }
+                            }
+                        }
+                    }
+                    j = j+2;
+                }
+                i = i+2;
+            }
+        }
+
+
+        /*************************************************************************
+        RMatrixGEMM kernel, basecase code for RMatrixGEMM.
+
+        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:
+        * 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.
+
+        IMPORTANT:
+
+        This function does NOT preallocate output matrix C, it MUST be preallocated
+        by caller prior to calling this function. In case C does not have  enough
+        space to store result, exception will be generated.
+
+        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       -   PREALLOCATED output matrix
+            IC      -   submatrix offset
+            JC      -   submatrix offset
+
+          -- ALGLIB routine --
+             27.03.2013
+             Bochkanov Sergey
+        *************************************************************************/
+        public 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,
+            double[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            
+            //
+            // if matrix size is zero
+            //
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            
+            //
+            // Try optimized code
+            //
+            if( rmatrixgemmf(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params) )
+            {
+                return;
+            }
+            
+            //
+            // if K=0 or Alpha=0, then C=Beta*C
+            //
+            if( k==0 || (double)(alpha)==(double)(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;
+            }
+            
+            //
+            // Call specialized code.
+            //
+            // NOTE: specialized code was moved to separate function because of strange
+            //       issues with instructions cache on some systems; Having too long
+            //       functions significantly slows down internal loop of the algorithm.
+            //
+            if( optypea==0 && optypeb==0 )
+            {
+                rmatrixgemmk44v00(m, n, k, alpha, a, ia, ja, b, ib, jb, beta, c, ic, jc, _params);
+            }
+            if( optypea==0 && optypeb!=0 )
+            {
+                rmatrixgemmk44v01(m, n, k, alpha, a, ia, ja, b, ib, jb, beta, c, ic, jc, _params);
+            }
+            if( optypea!=0 && optypeb==0 )
+            {
+                rmatrixgemmk44v10(m, n, k, alpha, a, ia, ja, b, ib, jb, beta, c, ic, jc, _params);
+            }
+            if( optypea!=0 && optypeb!=0 )
+            {
+                rmatrixgemmk44v11(m, n, k, alpha, a, ia, ja, b, ib, jb, beta, c, ic, jc, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        RMatrixGEMM kernel, basecase code for RMatrixGEMM, specialized for sitation
+        with OpTypeA=0 and OpTypeB=0.
+
+        Additional info:
+        * this function requires that Alpha<>0 (assertion is thrown otherwise)
+
+        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
+            B       -   matrix
+            IB      -   submatrix offset
+            JB      -   submatrix offset
+            Beta    -   coefficient
+            C       -   PREALLOCATED output matrix
+            IC      -   submatrix offset
+            JC      -   submatrix offset
+
+          -- ALGLIB routine --
+             27.03.2013
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixgemmk44v00(int m,
+            int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            double[,] b,
+            int ib,
+            int jb,
+            double beta,
+            double[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double v00 = 0;
+            double v01 = 0;
+            double v02 = 0;
+            double v03 = 0;
+            double v10 = 0;
+            double v11 = 0;
+            double v12 = 0;
+            double v13 = 0;
+            double v20 = 0;
+            double v21 = 0;
+            double v22 = 0;
+            double v23 = 0;
+            double v30 = 0;
+            double v31 = 0;
+            double v32 = 0;
+            double v33 = 0;
+            double a0 = 0;
+            double a1 = 0;
+            double a2 = 0;
+            double a3 = 0;
+            double b0 = 0;
+            double b1 = 0;
+            double b2 = 0;
+            double b3 = 0;
+            int idxa0 = 0;
+            int idxa1 = 0;
+            int idxa2 = 0;
+            int idxa3 = 0;
+            int idxb0 = 0;
+            int idxb1 = 0;
+            int idxb2 = 0;
+            int idxb3 = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int ik = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int jk = 0;
+            int t = 0;
+            int offsa = 0;
+            int offsb = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert((double)(alpha)!=(double)(0), "RMatrixGEMMK44V00: internal error (Alpha=0)");
+            
+            //
+            // if matrix size is zero
+            //
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            
+            //
+            // A*B
+            //
+            i = 0;
+            while( i<m )
+            {
+                j = 0;
+                while( j<n )
+                {
+                    
+                    //
+                    // Choose between specialized 4x4 code and general code
+                    //
+                    if( i+4<=m && j+4<=n )
+                    {
+                        
+                        //
+                        // Specialized 4x4 code for [I..I+3]x[J..J+3] submatrix of C.
+                        //
+                        // This submatrix is calculated as sum of K rank-1 products,
+                        // with operands cached in local variables in order to speed
+                        // up operations with arrays.
+                        //
+                        idxa0 = ia+i+0;
+                        idxa1 = ia+i+1;
+                        idxa2 = ia+i+2;
+                        idxa3 = ia+i+3;
+                        offsa = ja;
+                        idxb0 = jb+j+0;
+                        idxb1 = jb+j+1;
+                        idxb2 = jb+j+2;
+                        idxb3 = jb+j+3;
+                        offsb = ib;
+                        v00 = 0.0;
+                        v01 = 0.0;
+                        v02 = 0.0;
+                        v03 = 0.0;
+                        v10 = 0.0;
+                        v11 = 0.0;
+                        v12 = 0.0;
+                        v13 = 0.0;
+                        v20 = 0.0;
+                        v21 = 0.0;
+                        v22 = 0.0;
+                        v23 = 0.0;
+                        v30 = 0.0;
+                        v31 = 0.0;
+                        v32 = 0.0;
+                        v33 = 0.0;
+                        
+                        //
+                        // Different variants of internal loop
+                        //
+                        for(t=0; t<=k-1; t++)
+                        {
+                            a0 = a[idxa0,offsa];
+                            a1 = a[idxa1,offsa];
+                            b0 = b[offsb,idxb0];
+                            b1 = b[offsb,idxb1];
+                            v00 = v00+a0*b0;
+                            v01 = v01+a0*b1;
+                            v10 = v10+a1*b0;
+                            v11 = v11+a1*b1;
+                            a2 = a[idxa2,offsa];
+                            a3 = a[idxa3,offsa];
+                            v20 = v20+a2*b0;
+                            v21 = v21+a2*b1;
+                            v30 = v30+a3*b0;
+                            v31 = v31+a3*b1;
+                            b2 = b[offsb,idxb2];
+                            b3 = b[offsb,idxb3];
+                            v22 = v22+a2*b2;
+                            v23 = v23+a2*b3;
+                            v32 = v32+a3*b2;
+                            v33 = v33+a3*b3;
+                            v02 = v02+a0*b2;
+                            v03 = v03+a0*b3;
+                            v12 = v12+a1*b2;
+                            v13 = v13+a1*b3;
+                            offsa = offsa+1;
+                            offsb = offsb+1;
+                        }
+                        if( (double)(beta)==(double)(0) )
+                        {
+                            c[ic+i+0,jc+j+0] = alpha*v00;
+                            c[ic+i+0,jc+j+1] = alpha*v01;
+                            c[ic+i+0,jc+j+2] = alpha*v02;
+                            c[ic+i+0,jc+j+3] = alpha*v03;
+                            c[ic+i+1,jc+j+0] = alpha*v10;
+                            c[ic+i+1,jc+j+1] = alpha*v11;
+                            c[ic+i+1,jc+j+2] = alpha*v12;
+                            c[ic+i+1,jc+j+3] = alpha*v13;
+                            c[ic+i+2,jc+j+0] = alpha*v20;
+                            c[ic+i+2,jc+j+1] = alpha*v21;
+                            c[ic+i+2,jc+j+2] = alpha*v22;
+                            c[ic+i+2,jc+j+3] = alpha*v23;
+                            c[ic+i+3,jc+j+0] = alpha*v30;
+                            c[ic+i+3,jc+j+1] = alpha*v31;
+                            c[ic+i+3,jc+j+2] = alpha*v32;
+                            c[ic+i+3,jc+j+3] = alpha*v33;
+                        }
+                        else
+                        {
+                            c[ic+i+0,jc+j+0] = beta*c[ic+i+0,jc+j+0]+alpha*v00;
+                            c[ic+i+0,jc+j+1] = beta*c[ic+i+0,jc+j+1]+alpha*v01;
+                            c[ic+i+0,jc+j+2] = beta*c[ic+i+0,jc+j+2]+alpha*v02;
+                            c[ic+i+0,jc+j+3] = beta*c[ic+i+0,jc+j+3]+alpha*v03;
+                            c[ic+i+1,jc+j+0] = beta*c[ic+i+1,jc+j+0]+alpha*v10;
+                            c[ic+i+1,jc+j+1] = beta*c[ic+i+1,jc+j+1]+alpha*v11;
+                            c[ic+i+1,jc+j+2] = beta*c[ic+i+1,jc+j+2]+alpha*v12;
+                            c[ic+i+1,jc+j+3] = beta*c[ic+i+1,jc+j+3]+alpha*v13;
+                            c[ic+i+2,jc+j+0] = beta*c[ic+i+2,jc+j+0]+alpha*v20;
+                            c[ic+i+2,jc+j+1] = beta*c[ic+i+2,jc+j+1]+alpha*v21;
+                            c[ic+i+2,jc+j+2] = beta*c[ic+i+2,jc+j+2]+alpha*v22;
+                            c[ic+i+2,jc+j+3] = beta*c[ic+i+2,jc+j+3]+alpha*v23;
+                            c[ic+i+3,jc+j+0] = beta*c[ic+i+3,jc+j+0]+alpha*v30;
+                            c[ic+i+3,jc+j+1] = beta*c[ic+i+3,jc+j+1]+alpha*v31;
+                            c[ic+i+3,jc+j+2] = beta*c[ic+i+3,jc+j+2]+alpha*v32;
+                            c[ic+i+3,jc+j+3] = beta*c[ic+i+3,jc+j+3]+alpha*v33;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Determine submatrix [I0..I1]x[J0..J1] to process
+                        //
+                        i0 = i;
+                        i1 = Math.Min(i+3, m-1);
+                        j0 = j;
+                        j1 = Math.Min(j+3, n-1);
+                        
+                        //
+                        // Process submatrix
+                        //
+                        for(ik=i0; ik<=i1; ik++)
+                        {
+                            for(jk=j0; jk<=j1; jk++)
+                            {
+                                if( k==0 || (double)(alpha)==(double)(0) )
+                                {
+                                    v = 0;
+                                }
+                                else
+                                {
+                                    i1_ = (ib)-(ja);
+                                    v = 0.0;
+                                    for(i_=ja; i_<=ja+k-1;i_++)
+                                    {
+                                        v += a[ia+ik,i_]*b[i_+i1_,jb+jk];
+                                    }
+                                }
+                                if( (double)(beta)==(double)(0) )
+                                {
+                                    c[ic+ik,jc+jk] = alpha*v;
+                                }
+                                else
+                                {
+                                    c[ic+ik,jc+jk] = beta*c[ic+ik,jc+jk]+alpha*v;
+                                }
+                            }
+                        }
+                    }
+                    j = j+4;
+                }
+                i = i+4;
+            }
+        }
+
+
+        /*************************************************************************
+        RMatrixGEMM kernel, basecase code for RMatrixGEMM, specialized for sitation
+        with OpTypeA=0 and OpTypeB=1.
+
+        Additional info:
+        * this function requires that Alpha<>0 (assertion is thrown otherwise)
+
+        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
+            B       -   matrix
+            IB      -   submatrix offset
+            JB      -   submatrix offset
+            Beta    -   coefficient
+            C       -   PREALLOCATED output matrix
+            IC      -   submatrix offset
+            JC      -   submatrix offset
+
+          -- ALGLIB routine --
+             27.03.2013
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixgemmk44v01(int m,
+            int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            double[,] b,
+            int ib,
+            int jb,
+            double beta,
+            double[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double v00 = 0;
+            double v01 = 0;
+            double v02 = 0;
+            double v03 = 0;
+            double v10 = 0;
+            double v11 = 0;
+            double v12 = 0;
+            double v13 = 0;
+            double v20 = 0;
+            double v21 = 0;
+            double v22 = 0;
+            double v23 = 0;
+            double v30 = 0;
+            double v31 = 0;
+            double v32 = 0;
+            double v33 = 0;
+            double a0 = 0;
+            double a1 = 0;
+            double a2 = 0;
+            double a3 = 0;
+            double b0 = 0;
+            double b1 = 0;
+            double b2 = 0;
+            double b3 = 0;
+            int idxa0 = 0;
+            int idxa1 = 0;
+            int idxa2 = 0;
+            int idxa3 = 0;
+            int idxb0 = 0;
+            int idxb1 = 0;
+            int idxb2 = 0;
+            int idxb3 = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int ik = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int jk = 0;
+            int t = 0;
+            int offsa = 0;
+            int offsb = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert((double)(alpha)!=(double)(0), "RMatrixGEMMK44V00: internal error (Alpha=0)");
+            
+            //
+            // if matrix size is zero
+            //
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            
+            //
+            // A*B'
+            //
+            i = 0;
+            while( i<m )
+            {
+                j = 0;
+                while( j<n )
+                {
+                    
+                    //
+                    // Choose between specialized 4x4 code and general code
+                    //
+                    if( i+4<=m && j+4<=n )
+                    {
+                        
+                        //
+                        // Specialized 4x4 code for [I..I+3]x[J..J+3] submatrix of C.
+                        //
+                        // This submatrix is calculated as sum of K rank-1 products,
+                        // with operands cached in local variables in order to speed
+                        // up operations with arrays.
+                        //
+                        idxa0 = ia+i+0;
+                        idxa1 = ia+i+1;
+                        idxa2 = ia+i+2;
+                        idxa3 = ia+i+3;
+                        offsa = ja;
+                        idxb0 = ib+j+0;
+                        idxb1 = ib+j+1;
+                        idxb2 = ib+j+2;
+                        idxb3 = ib+j+3;
+                        offsb = jb;
+                        v00 = 0.0;
+                        v01 = 0.0;
+                        v02 = 0.0;
+                        v03 = 0.0;
+                        v10 = 0.0;
+                        v11 = 0.0;
+                        v12 = 0.0;
+                        v13 = 0.0;
+                        v20 = 0.0;
+                        v21 = 0.0;
+                        v22 = 0.0;
+                        v23 = 0.0;
+                        v30 = 0.0;
+                        v31 = 0.0;
+                        v32 = 0.0;
+                        v33 = 0.0;
+                        for(t=0; t<=k-1; t++)
+                        {
+                            a0 = a[idxa0,offsa];
+                            a1 = a[idxa1,offsa];
+                            b0 = b[idxb0,offsb];
+                            b1 = b[idxb1,offsb];
+                            v00 = v00+a0*b0;
+                            v01 = v01+a0*b1;
+                            v10 = v10+a1*b0;
+                            v11 = v11+a1*b1;
+                            a2 = a[idxa2,offsa];
+                            a3 = a[idxa3,offsa];
+                            v20 = v20+a2*b0;
+                            v21 = v21+a2*b1;
+                            v30 = v30+a3*b0;
+                            v31 = v31+a3*b1;
+                            b2 = b[idxb2,offsb];
+                            b3 = b[idxb3,offsb];
+                            v22 = v22+a2*b2;
+                            v23 = v23+a2*b3;
+                            v32 = v32+a3*b2;
+                            v33 = v33+a3*b3;
+                            v02 = v02+a0*b2;
+                            v03 = v03+a0*b3;
+                            v12 = v12+a1*b2;
+                            v13 = v13+a1*b3;
+                            offsa = offsa+1;
+                            offsb = offsb+1;
+                        }
+                        if( (double)(beta)==(double)(0) )
+                        {
+                            c[ic+i+0,jc+j+0] = alpha*v00;
+                            c[ic+i+0,jc+j+1] = alpha*v01;
+                            c[ic+i+0,jc+j+2] = alpha*v02;
+                            c[ic+i+0,jc+j+3] = alpha*v03;
+                            c[ic+i+1,jc+j+0] = alpha*v10;
+                            c[ic+i+1,jc+j+1] = alpha*v11;
+                            c[ic+i+1,jc+j+2] = alpha*v12;
+                            c[ic+i+1,jc+j+3] = alpha*v13;
+                            c[ic+i+2,jc+j+0] = alpha*v20;
+                            c[ic+i+2,jc+j+1] = alpha*v21;
+                            c[ic+i+2,jc+j+2] = alpha*v22;
+                            c[ic+i+2,jc+j+3] = alpha*v23;
+                            c[ic+i+3,jc+j+0] = alpha*v30;
+                            c[ic+i+3,jc+j+1] = alpha*v31;
+                            c[ic+i+3,jc+j+2] = alpha*v32;
+                            c[ic+i+3,jc+j+3] = alpha*v33;
+                        }
+                        else
+                        {
+                            c[ic+i+0,jc+j+0] = beta*c[ic+i+0,jc+j+0]+alpha*v00;
+                            c[ic+i+0,jc+j+1] = beta*c[ic+i+0,jc+j+1]+alpha*v01;
+                            c[ic+i+0,jc+j+2] = beta*c[ic+i+0,jc+j+2]+alpha*v02;
+                            c[ic+i+0,jc+j+3] = beta*c[ic+i+0,jc+j+3]+alpha*v03;
+                            c[ic+i+1,jc+j+0] = beta*c[ic+i+1,jc+j+0]+alpha*v10;
+                            c[ic+i+1,jc+j+1] = beta*c[ic+i+1,jc+j+1]+alpha*v11;
+                            c[ic+i+1,jc+j+2] = beta*c[ic+i+1,jc+j+2]+alpha*v12;
+                            c[ic+i+1,jc+j+3] = beta*c[ic+i+1,jc+j+3]+alpha*v13;
+                            c[ic+i+2,jc+j+0] = beta*c[ic+i+2,jc+j+0]+alpha*v20;
+                            c[ic+i+2,jc+j+1] = beta*c[ic+i+2,jc+j+1]+alpha*v21;
+                            c[ic+i+2,jc+j+2] = beta*c[ic+i+2,jc+j+2]+alpha*v22;
+                            c[ic+i+2,jc+j+3] = beta*c[ic+i+2,jc+j+3]+alpha*v23;
+                            c[ic+i+3,jc+j+0] = beta*c[ic+i+3,jc+j+0]+alpha*v30;
+                            c[ic+i+3,jc+j+1] = beta*c[ic+i+3,jc+j+1]+alpha*v31;
+                            c[ic+i+3,jc+j+2] = beta*c[ic+i+3,jc+j+2]+alpha*v32;
+                            c[ic+i+3,jc+j+3] = beta*c[ic+i+3,jc+j+3]+alpha*v33;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Determine submatrix [I0..I1]x[J0..J1] to process
+                        //
+                        i0 = i;
+                        i1 = Math.Min(i+3, m-1);
+                        j0 = j;
+                        j1 = Math.Min(j+3, n-1);
+                        
+                        //
+                        // Process submatrix
+                        //
+                        for(ik=i0; ik<=i1; ik++)
+                        {
+                            for(jk=j0; jk<=j1; jk++)
+                            {
+                                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+ik,i_]*b[ib+jk,i_+i1_];
+                                    }
+                                }
+                                if( (double)(beta)==(double)(0) )
+                                {
+                                    c[ic+ik,jc+jk] = alpha*v;
+                                }
+                                else
+                                {
+                                    c[ic+ik,jc+jk] = beta*c[ic+ik,jc+jk]+alpha*v;
+                                }
+                            }
+                        }
+                    }
+                    j = j+4;
+                }
+                i = i+4;
+            }
+        }
+
+
+        /*************************************************************************
+        RMatrixGEMM kernel, basecase code for RMatrixGEMM, specialized for sitation
+        with OpTypeA=1 and OpTypeB=0.
+
+        Additional info:
+        * this function requires that Alpha<>0 (assertion is thrown otherwise)
+
+        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
+            B       -   matrix
+            IB      -   submatrix offset
+            JB      -   submatrix offset
+            Beta    -   coefficient
+            C       -   PREALLOCATED output matrix
+            IC      -   submatrix offset
+            JC      -   submatrix offset
+
+          -- ALGLIB routine --
+             27.03.2013
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixgemmk44v10(int m,
+            int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            double[,] b,
+            int ib,
+            int jb,
+            double beta,
+            double[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double v00 = 0;
+            double v01 = 0;
+            double v02 = 0;
+            double v03 = 0;
+            double v10 = 0;
+            double v11 = 0;
+            double v12 = 0;
+            double v13 = 0;
+            double v20 = 0;
+            double v21 = 0;
+            double v22 = 0;
+            double v23 = 0;
+            double v30 = 0;
+            double v31 = 0;
+            double v32 = 0;
+            double v33 = 0;
+            double a0 = 0;
+            double a1 = 0;
+            double a2 = 0;
+            double a3 = 0;
+            double b0 = 0;
+            double b1 = 0;
+            double b2 = 0;
+            double b3 = 0;
+            int idxa0 = 0;
+            int idxa1 = 0;
+            int idxa2 = 0;
+            int idxa3 = 0;
+            int idxb0 = 0;
+            int idxb1 = 0;
+            int idxb2 = 0;
+            int idxb3 = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int ik = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int jk = 0;
+            int t = 0;
+            int offsa = 0;
+            int offsb = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert((double)(alpha)!=(double)(0), "RMatrixGEMMK44V00: internal error (Alpha=0)");
+            
+            //
+            // if matrix size is zero
+            //
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            
+            //
+            // A'*B
+            //
+            i = 0;
+            while( i<m )
+            {
+                j = 0;
+                while( j<n )
+                {
+                    
+                    //
+                    // Choose between specialized 4x4 code and general code
+                    //
+                    if( i+4<=m && j+4<=n )
+                    {
+                        
+                        //
+                        // Specialized 4x4 code for [I..I+3]x[J..J+3] submatrix of C.
+                        //
+                        // This submatrix is calculated as sum of K rank-1 products,
+                        // with operands cached in local variables in order to speed
+                        // up operations with arrays.
+                        //
+                        idxa0 = ja+i+0;
+                        idxa1 = ja+i+1;
+                        idxa2 = ja+i+2;
+                        idxa3 = ja+i+3;
+                        offsa = ia;
+                        idxb0 = jb+j+0;
+                        idxb1 = jb+j+1;
+                        idxb2 = jb+j+2;
+                        idxb3 = jb+j+3;
+                        offsb = ib;
+                        v00 = 0.0;
+                        v01 = 0.0;
+                        v02 = 0.0;
+                        v03 = 0.0;
+                        v10 = 0.0;
+                        v11 = 0.0;
+                        v12 = 0.0;
+                        v13 = 0.0;
+                        v20 = 0.0;
+                        v21 = 0.0;
+                        v22 = 0.0;
+                        v23 = 0.0;
+                        v30 = 0.0;
+                        v31 = 0.0;
+                        v32 = 0.0;
+                        v33 = 0.0;
+                        for(t=0; t<=k-1; t++)
+                        {
+                            a0 = a[offsa,idxa0];
+                            a1 = a[offsa,idxa1];
+                            b0 = b[offsb,idxb0];
+                            b1 = b[offsb,idxb1];
+                            v00 = v00+a0*b0;
+                            v01 = v01+a0*b1;
+                            v10 = v10+a1*b0;
+                            v11 = v11+a1*b1;
+                            a2 = a[offsa,idxa2];
+                            a3 = a[offsa,idxa3];
+                            v20 = v20+a2*b0;
+                            v21 = v21+a2*b1;
+                            v30 = v30+a3*b0;
+                            v31 = v31+a3*b1;
+                            b2 = b[offsb,idxb2];
+                            b3 = b[offsb,idxb3];
+                            v22 = v22+a2*b2;
+                            v23 = v23+a2*b3;
+                            v32 = v32+a3*b2;
+                            v33 = v33+a3*b3;
+                            v02 = v02+a0*b2;
+                            v03 = v03+a0*b3;
+                            v12 = v12+a1*b2;
+                            v13 = v13+a1*b3;
+                            offsa = offsa+1;
+                            offsb = offsb+1;
+                        }
+                        if( (double)(beta)==(double)(0) )
+                        {
+                            c[ic+i+0,jc+j+0] = alpha*v00;
+                            c[ic+i+0,jc+j+1] = alpha*v01;
+                            c[ic+i+0,jc+j+2] = alpha*v02;
+                            c[ic+i+0,jc+j+3] = alpha*v03;
+                            c[ic+i+1,jc+j+0] = alpha*v10;
+                            c[ic+i+1,jc+j+1] = alpha*v11;
+                            c[ic+i+1,jc+j+2] = alpha*v12;
+                            c[ic+i+1,jc+j+3] = alpha*v13;
+                            c[ic+i+2,jc+j+0] = alpha*v20;
+                            c[ic+i+2,jc+j+1] = alpha*v21;
+                            c[ic+i+2,jc+j+2] = alpha*v22;
+                            c[ic+i+2,jc+j+3] = alpha*v23;
+                            c[ic+i+3,jc+j+0] = alpha*v30;
+                            c[ic+i+3,jc+j+1] = alpha*v31;
+                            c[ic+i+3,jc+j+2] = alpha*v32;
+                            c[ic+i+3,jc+j+3] = alpha*v33;
+                        }
+                        else
+                        {
+                            c[ic+i+0,jc+j+0] = beta*c[ic+i+0,jc+j+0]+alpha*v00;
+                            c[ic+i+0,jc+j+1] = beta*c[ic+i+0,jc+j+1]+alpha*v01;
+                            c[ic+i+0,jc+j+2] = beta*c[ic+i+0,jc+j+2]+alpha*v02;
+                            c[ic+i+0,jc+j+3] = beta*c[ic+i+0,jc+j+3]+alpha*v03;
+                            c[ic+i+1,jc+j+0] = beta*c[ic+i+1,jc+j+0]+alpha*v10;
+                            c[ic+i+1,jc+j+1] = beta*c[ic+i+1,jc+j+1]+alpha*v11;
+                            c[ic+i+1,jc+j+2] = beta*c[ic+i+1,jc+j+2]+alpha*v12;
+                            c[ic+i+1,jc+j+3] = beta*c[ic+i+1,jc+j+3]+alpha*v13;
+                            c[ic+i+2,jc+j+0] = beta*c[ic+i+2,jc+j+0]+alpha*v20;
+                            c[ic+i+2,jc+j+1] = beta*c[ic+i+2,jc+j+1]+alpha*v21;
+                            c[ic+i+2,jc+j+2] = beta*c[ic+i+2,jc+j+2]+alpha*v22;
+                            c[ic+i+2,jc+j+3] = beta*c[ic+i+2,jc+j+3]+alpha*v23;
+                            c[ic+i+3,jc+j+0] = beta*c[ic+i+3,jc+j+0]+alpha*v30;
+                            c[ic+i+3,jc+j+1] = beta*c[ic+i+3,jc+j+1]+alpha*v31;
+                            c[ic+i+3,jc+j+2] = beta*c[ic+i+3,jc+j+2]+alpha*v32;
+                            c[ic+i+3,jc+j+3] = beta*c[ic+i+3,jc+j+3]+alpha*v33;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Determine submatrix [I0..I1]x[J0..J1] to process
+                        //
+                        i0 = i;
+                        i1 = Math.Min(i+3, m-1);
+                        j0 = j;
+                        j1 = Math.Min(j+3, n-1);
+                        
+                        //
+                        // Process submatrix
+                        //
+                        for(ik=i0; ik<=i1; ik++)
+                        {
+                            for(jk=j0; jk<=j1; jk++)
+                            {
+                                if( k==0 || (double)(alpha)==(double)(0) )
+                                {
+                                    v = 0;
+                                }
+                                else
+                                {
+                                    v = 0.0;
+                                    i1_ = (ib)-(ia);
+                                    v = 0.0;
+                                    for(i_=ia; i_<=ia+k-1;i_++)
+                                    {
+                                        v += a[i_,ja+ik]*b[i_+i1_,jb+jk];
+                                    }
+                                }
+                                if( (double)(beta)==(double)(0) )
+                                {
+                                    c[ic+ik,jc+jk] = alpha*v;
+                                }
+                                else
+                                {
+                                    c[ic+ik,jc+jk] = beta*c[ic+ik,jc+jk]+alpha*v;
+                                }
+                            }
+                        }
+                    }
+                    j = j+4;
+                }
+                i = i+4;
+            }
+        }
+
+
+        /*************************************************************************
+        RMatrixGEMM kernel, basecase code for RMatrixGEMM, specialized for sitation
+        with OpTypeA=1 and OpTypeB=1.
+
+        Additional info:
+        * this function requires that Alpha<>0 (assertion is thrown otherwise)
+
+        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
+            B       -   matrix
+            IB      -   submatrix offset
+            JB      -   submatrix offset
+            Beta    -   coefficient
+            C       -   PREALLOCATED output matrix
+            IC      -   submatrix offset
+            JC      -   submatrix offset
+
+          -- ALGLIB routine --
+             27.03.2013
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixgemmk44v11(int m,
+            int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            double[,] b,
+            int ib,
+            int jb,
+            double beta,
+            double[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double v00 = 0;
+            double v01 = 0;
+            double v02 = 0;
+            double v03 = 0;
+            double v10 = 0;
+            double v11 = 0;
+            double v12 = 0;
+            double v13 = 0;
+            double v20 = 0;
+            double v21 = 0;
+            double v22 = 0;
+            double v23 = 0;
+            double v30 = 0;
+            double v31 = 0;
+            double v32 = 0;
+            double v33 = 0;
+            double a0 = 0;
+            double a1 = 0;
+            double a2 = 0;
+            double a3 = 0;
+            double b0 = 0;
+            double b1 = 0;
+            double b2 = 0;
+            double b3 = 0;
+            int idxa0 = 0;
+            int idxa1 = 0;
+            int idxa2 = 0;
+            int idxa3 = 0;
+            int idxb0 = 0;
+            int idxb1 = 0;
+            int idxb2 = 0;
+            int idxb3 = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int ik = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int jk = 0;
+            int t = 0;
+            int offsa = 0;
+            int offsb = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert((double)(alpha)!=(double)(0), "RMatrixGEMMK44V00: internal error (Alpha=0)");
+            
+            //
+            // if matrix size is zero
+            //
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            
+            //
+            // A'*B'
+            //
+            i = 0;
+            while( i<m )
+            {
+                j = 0;
+                while( j<n )
+                {
+                    
+                    //
+                    // Choose between specialized 4x4 code and general code
+                    //
+                    if( i+4<=m && j+4<=n )
+                    {
+                        
+                        //
+                        // Specialized 4x4 code for [I..I+3]x[J..J+3] submatrix of C.
+                        //
+                        // This submatrix is calculated as sum of K rank-1 products,
+                        // with operands cached in local variables in order to speed
+                        // up operations with arrays.
+                        //
+                        idxa0 = ja+i+0;
+                        idxa1 = ja+i+1;
+                        idxa2 = ja+i+2;
+                        idxa3 = ja+i+3;
+                        offsa = ia;
+                        idxb0 = ib+j+0;
+                        idxb1 = ib+j+1;
+                        idxb2 = ib+j+2;
+                        idxb3 = ib+j+3;
+                        offsb = jb;
+                        v00 = 0.0;
+                        v01 = 0.0;
+                        v02 = 0.0;
+                        v03 = 0.0;
+                        v10 = 0.0;
+                        v11 = 0.0;
+                        v12 = 0.0;
+                        v13 = 0.0;
+                        v20 = 0.0;
+                        v21 = 0.0;
+                        v22 = 0.0;
+                        v23 = 0.0;
+                        v30 = 0.0;
+                        v31 = 0.0;
+                        v32 = 0.0;
+                        v33 = 0.0;
+                        for(t=0; t<=k-1; t++)
+                        {
+                            a0 = a[offsa,idxa0];
+                            a1 = a[offsa,idxa1];
+                            b0 = b[idxb0,offsb];
+                            b1 = b[idxb1,offsb];
+                            v00 = v00+a0*b0;
+                            v01 = v01+a0*b1;
+                            v10 = v10+a1*b0;
+                            v11 = v11+a1*b1;
+                            a2 = a[offsa,idxa2];
+                            a3 = a[offsa,idxa3];
+                            v20 = v20+a2*b0;
+                            v21 = v21+a2*b1;
+                            v30 = v30+a3*b0;
+                            v31 = v31+a3*b1;
+                            b2 = b[idxb2,offsb];
+                            b3 = b[idxb3,offsb];
+                            v22 = v22+a2*b2;
+                            v23 = v23+a2*b3;
+                            v32 = v32+a3*b2;
+                            v33 = v33+a3*b3;
+                            v02 = v02+a0*b2;
+                            v03 = v03+a0*b3;
+                            v12 = v12+a1*b2;
+                            v13 = v13+a1*b3;
+                            offsa = offsa+1;
+                            offsb = offsb+1;
+                        }
+                        if( (double)(beta)==(double)(0) )
+                        {
+                            c[ic+i+0,jc+j+0] = alpha*v00;
+                            c[ic+i+0,jc+j+1] = alpha*v01;
+                            c[ic+i+0,jc+j+2] = alpha*v02;
+                            c[ic+i+0,jc+j+3] = alpha*v03;
+                            c[ic+i+1,jc+j+0] = alpha*v10;
+                            c[ic+i+1,jc+j+1] = alpha*v11;
+                            c[ic+i+1,jc+j+2] = alpha*v12;
+                            c[ic+i+1,jc+j+3] = alpha*v13;
+                            c[ic+i+2,jc+j+0] = alpha*v20;
+                            c[ic+i+2,jc+j+1] = alpha*v21;
+                            c[ic+i+2,jc+j+2] = alpha*v22;
+                            c[ic+i+2,jc+j+3] = alpha*v23;
+                            c[ic+i+3,jc+j+0] = alpha*v30;
+                            c[ic+i+3,jc+j+1] = alpha*v31;
+                            c[ic+i+3,jc+j+2] = alpha*v32;
+                            c[ic+i+3,jc+j+3] = alpha*v33;
+                        }
+                        else
+                        {
+                            c[ic+i+0,jc+j+0] = beta*c[ic+i+0,jc+j+0]+alpha*v00;
+                            c[ic+i+0,jc+j+1] = beta*c[ic+i+0,jc+j+1]+alpha*v01;
+                            c[ic+i+0,jc+j+2] = beta*c[ic+i+0,jc+j+2]+alpha*v02;
+                            c[ic+i+0,jc+j+3] = beta*c[ic+i+0,jc+j+3]+alpha*v03;
+                            c[ic+i+1,jc+j+0] = beta*c[ic+i+1,jc+j+0]+alpha*v10;
+                            c[ic+i+1,jc+j+1] = beta*c[ic+i+1,jc+j+1]+alpha*v11;
+                            c[ic+i+1,jc+j+2] = beta*c[ic+i+1,jc+j+2]+alpha*v12;
+                            c[ic+i+1,jc+j+3] = beta*c[ic+i+1,jc+j+3]+alpha*v13;
+                            c[ic+i+2,jc+j+0] = beta*c[ic+i+2,jc+j+0]+alpha*v20;
+                            c[ic+i+2,jc+j+1] = beta*c[ic+i+2,jc+j+1]+alpha*v21;
+                            c[ic+i+2,jc+j+2] = beta*c[ic+i+2,jc+j+2]+alpha*v22;
+                            c[ic+i+2,jc+j+3] = beta*c[ic+i+2,jc+j+3]+alpha*v23;
+                            c[ic+i+3,jc+j+0] = beta*c[ic+i+3,jc+j+0]+alpha*v30;
+                            c[ic+i+3,jc+j+1] = beta*c[ic+i+3,jc+j+1]+alpha*v31;
+                            c[ic+i+3,jc+j+2] = beta*c[ic+i+3,jc+j+2]+alpha*v32;
+                            c[ic+i+3,jc+j+3] = beta*c[ic+i+3,jc+j+3]+alpha*v33;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Determine submatrix [I0..I1]x[J0..J1] to process
+                        //
+                        i0 = i;
+                        i1 = Math.Min(i+3, m-1);
+                        j0 = j;
+                        j1 = Math.Min(j+3, n-1);
+                        
+                        //
+                        // Process submatrix
+                        //
+                        for(ik=i0; ik<=i1; ik++)
+                        {
+                            for(jk=j0; jk<=j1; jk++)
+                            {
+                                if( k==0 || (double)(alpha)==(double)(0) )
+                                {
+                                    v = 0;
+                                }
+                                else
+                                {
+                                    v = 0.0;
+                                    i1_ = (jb)-(ia);
+                                    v = 0.0;
+                                    for(i_=ia; i_<=ia+k-1;i_++)
+                                    {
+                                        v += a[i_,ja+ik]*b[ib+jk,i_+i1_];
+                                    }
+                                }
+                                if( (double)(beta)==(double)(0) )
+                                {
+                                    c[ic+ik,jc+jk] = alpha*v;
+                                }
+                                else
+                                {
+                                    c[ic+ik,jc+jk] = beta*c[ic+ik,jc+jk]+alpha*v;
+                                }
+                            }
+                        }
+                    }
+                    j = j+4;
+                }
+                i = i+4;
+            }
+        }
+
+
+    }
+    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  doesn't
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            complex t = 0;
+            int i = 0;
+            int i_ = 0;
+
+            if( (tau==0 || n1>n2) || m1>m2 )
+            {
+                return;
+            }
+            
+            //
+            // w := C^T * conj(v)
+            //
+            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,
+            alglib.xparams _params)
+        {
+            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 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,
+            double[,] a,
+            double[] work,
+            alglib.xparams _params)
+        {
+            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,
+            double[,] a,
+            double[] work,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                    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, _params);
+                    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, _params);
+                    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, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                    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, _params);
+                    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, _params);
+                    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, _params);
+                    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, _params);
+                    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, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            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 hblas
+    {
+        public static void hermitianmatrixvectormultiply(complex[,] a,
+            bool isupper,
+            int i1,
+            int i2,
+            complex[] x,
+            complex alpha,
+            ref complex[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int ba1 = 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;
+                    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;
+                    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;
+                    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;
+                    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,
+            alglib.xparams _params)
+        {
+            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 sblas
+    {
+        public static void symmetricmatrixvectormultiply(double[,] a,
+            bool isupper,
+            int i1,
+            int i2,
+            double[] x,
+            double alpha,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            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_];
+            }
+            apserv.touchint(ref ba2, _params);
+        }
+
+
+        public static void symmetricrank2update(ref double[,] a,
+            bool isupper,
+            int i1,
+            int i2,
+            double[] x,
+            double[] y,
+            ref double[] t,
+            double alpha,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+
+            result = i1;
+            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,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+
+            result = i1;
+            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,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int j = 0;
+
+            result = j1;
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int arows = 0;
+            int acols = 0;
+            int brows = 0;
+            int bcols = 0;
+            int crows = 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;
+            
+            //
+            // 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 linmin
+    {
+        public class linminstate : apobject
+        {
+            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 linminstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                linminstate _result = new linminstate();
+                _result.brackt = brackt;
+                _result.stage1 = stage1;
+                _result.infoc = infoc;
+                _result.dg = dg;
+                _result.dgm = dgm;
+                _result.dginit = dginit;
+                _result.dgtest = dgtest;
+                _result.dgx = dgx;
+                _result.dgxm = dgxm;
+                _result.dgy = dgy;
+                _result.dgym = dgym;
+                _result.finit = finit;
+                _result.ftest1 = ftest1;
+                _result.fm = fm;
+                _result.fx = fx;
+                _result.fxm = fxm;
+                _result.fy = fy;
+                _result.fym = fym;
+                _result.stx = stx;
+                _result.sty = sty;
+                _result.stmin = stmin;
+                _result.stmax = stmax;
+                _result.width = width;
+                _result.width1 = width1;
+                _result.xtrapf = xtrapf;
+                return _result;
+            }
+        };
+
+
+        public class armijostate : apobject
+        {
+            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()
+            {
+                init();
+            }
+            public override void init()
+            {
+                x = new double[0];
+                xbase = new double[0];
+                s = new double[0];
+                rstate = new rcommstate();
+            }
+            public override alglib.apobject make_copy()
+            {
+                armijostate _result = new armijostate();
+                _result.needf = needf;
+                _result.x = (double[])x.Clone();
+                _result.f = f;
+                _result.n = n;
+                _result.xbase = (double[])xbase.Clone();
+                _result.s = (double[])s.Clone();
+                _result.stplen = stplen;
+                _result.fcur = fcur;
+                _result.stpmax = stpmax;
+                _result.fmax = fmax;
+                _result.nfev = nfev;
+                _result.info = info;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                return _result;
+            }
+        };
+
+
+
+
+        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,
+            alglib.xparams _params)
+        {
+            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=StpMax
+
+        NOTE 3:
+
+        this algorithm guarantees that, if MCINFO=1 or MCINFO=5, then:
+        * F(final_point)<F(initial_point) - strict inequality
+        * final_point<>initial_point - after rounding to machine precision
+
+        NOTE 4:
+
+        when non-descent direction is specified, algorithm stops with MCINFO=0,
+        Stp=0 and initial point at X[].
+        :::::::::::::::::::::::::::::::::::::::::
+
+
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            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 = stpmax;
+                        stage = 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;
+                        stp = 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.finit)) && (double)(f)<=(double)(state.ftest1)) && (double)(state.dg)<=(double)(state.dgtest) )
+                    {
+                        info = 5;
+                    }
+                    if( (double)(stp)==(double)(stpmin) && (((double)(f)>=(double)(state.finit) || (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.finit) && (double)(f)<=(double)(state.ftest1)) && (double)(Math.Abs(state.dg))<=(double)(-(gtol*state.dginit)) )
+                    {
+                        info = 1;
+                    }
+                    
+                    //
+                    //        CHECK FOR TERMINATION.
+                    //
+                    if( info!=0 )
+                    {
+                        
+                        //
+                        // Check guarantees provided by the function for INFO=1 or INFO=5
+                        //
+                        if( info==1 || info==5 )
+                        {
+                            v = 0.0;
+                            for(i=0; i<=n-1; i++)
+                            {
+                                v = v+(wa[i]-x[i])*(wa[i]-x[i]);
+                            }
+                            if( (double)(f)>=(double)(state.finit) || (double)(v)==(double)(0.0) )
+                            {
+                                info = 6;
+                            }
+                        }
+                        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, _params);
+                        
+                        //
+                        //           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, _params);
+                    }
+                    
+                    //
+                    //        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 = 359;
+                v = -58;
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            }
+            
+            //
+            // 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, _params);
+            }
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            if( !math.isfinite(s) )
+            {
+                
+                //
+                // Overflow or underflow during evaluation of S; fallback low-precision code
+                //
+                r = 0;
+                rerr = mx*math.machineepsilon;
+                for(i=0; i<=n-1; i++)
+                {
+                    r = r+w[i];
+                }
+                return;
+            }
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = 0;
+            if( n>0 )
+            {
+                if( n%2==0 )
+                {
+                    result = math.sqr(xfastpow(r, n/2, _params));
+                }
+                else
+                {
+                    result = r*xfastpow(r, n-1, _params);
+                }
+                return result;
+            }
+            if( n==0 )
+            {
+                result = 1;
+            }
+            if( n<0 )
+            {
+                result = xfastpow(1/r, -n, _params);
+            }
+            return result;
+        }
+
+
+    }
+    public class basicstatops
+    {
+        /*************************************************************************
+        Internal tied ranking subroutine.
+
+        INPUT PARAMETERS:
+            X       -   array to rank
+            N       -   array size
+            IsCentered- whether ranks are centered or not:
+                        * True      -   ranks are centered in such way that  their
+                                        sum is zero
+                        * False     -   ranks are not centered
+            Buf     -   temporary buffers
+            
+        NOTE: when IsCentered is True and all X[] are equal, this  function  fills
+              X by zeros (exact zeros are used, not sum which is only approximately
+              equal to zero).
+        *************************************************************************/
+        public static void rankx(double[] x,
+            int n,
+            bool iscentered,
+            apserv.apbuffers buf,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double tmp = 0;
+            double voffs = 0;
+
+            
+            //
+            // Prepare
+            //
+            if( n<1 )
+            {
+                return;
+            }
+            if( n==1 )
+            {
+                x[0] = 0;
+                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;
+            }
+            tsort.tagsortfasti(ref buf.ra1, ref buf.ia1, ref buf.ra2, ref buf.ia2, n, _params);
+            
+            //
+            // Special test for all values being equal
+            //
+            if( (double)(buf.ra1[0])==(double)(buf.ra1[n-1]) )
+            {
+                if( iscentered )
+                {
+                    tmp = 0.0;
+                }
+                else
+                {
+                    tmp = (double)(n-1)/(double)2;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    x[i] = tmp;
+                }
+                return;
+            }
+            
+            //
+            // 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] = (double)(i+j-1)/(double)2;
+                }
+                i = j;
+            }
+            
+            //
+            // back to x
+            //
+            if( iscentered )
+            {
+                voffs = (double)(n-1)/(double)2;
+            }
+            else
+            {
+                voffs = 0.0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                x[buf.ia1[i]] = buf.ra1[i]-voffs;
+            }
+        }
+
+
+        /*************************************************************************
+        Internal untied ranking subroutine.
+
+        INPUT PARAMETERS:
+            X       -   array to rank
+            N       -   array size
+            Buf     -   temporary buffers
+
+        Returns untied ranks (in case of a tie ranks are resolved arbitrarily).
+        *************************************************************************/
+        public static void rankxuntied(double[] x,
+            int n,
+            apserv.apbuffers buf,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            
+            //
+            // Prepare
+            //
+            if( n<1 )
+            {
+                return;
+            }
+            if( n==1 )
+            {
+                x[0] = 0;
+                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;
+            }
+            tsort.tagsortfasti(ref buf.ra1, ref buf.ia1, ref buf.ra2, ref buf.ia2, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                x[buf.ia1[i]] = i;
+            }
+        }
+
+
+    }
+    public class hpccores
+    {
+        /*************************************************************************
+        This structure stores  temporary  buffers  used  by  gradient  calculation
+        functions for neural networks.
+        *************************************************************************/
+        public class mlpbuffers : apobject
+        {
+            public int chunksize;
+            public int ntotal;
+            public int nin;
+            public int nout;
+            public int wcount;
+            public double[] batch4buf;
+            public double[] hpcbuf;
+            public double[,] xy;
+            public double[,] xy2;
+            public double[] xyrow;
+            public double[] x;
+            public double[] y;
+            public double[] desiredy;
+            public double e;
+            public double[] g;
+            public double[] tmp0;
+            public mlpbuffers()
+            {
+                init();
+            }
+            public override void init()
+            {
+                batch4buf = new double[0];
+                hpcbuf = new double[0];
+                xy = new double[0,0];
+                xy2 = new double[0,0];
+                xyrow = new double[0];
+                x = new double[0];
+                y = new double[0];
+                desiredy = new double[0];
+                g = new double[0];
+                tmp0 = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                mlpbuffers _result = new mlpbuffers();
+                _result.chunksize = chunksize;
+                _result.ntotal = ntotal;
+                _result.nin = nin;
+                _result.nout = nout;
+                _result.wcount = wcount;
+                _result.batch4buf = (double[])batch4buf.Clone();
+                _result.hpcbuf = (double[])hpcbuf.Clone();
+                _result.xy = (double[,])xy.Clone();
+                _result.xy2 = (double[,])xy2.Clone();
+                _result.xyrow = (double[])xyrow.Clone();
+                _result.x = (double[])x.Clone();
+                _result.y = (double[])y.Clone();
+                _result.desiredy = (double[])desiredy.Clone();
+                _result.e = e;
+                _result.g = (double[])g.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        Prepares HPC compuations  of  chunked  gradient with HPCChunkedGradient().
+        You  have to call this function  before  calling  HPCChunkedGradient() for
+        a new set of weights. You have to call it only once, see example below:
+
+        HOW TO PROCESS DATASET WITH THIS FUNCTION:
+            Grad:=0
+            HPCPrepareChunkedGradient(Weights, WCount, NTotal, NOut, Buf)
+            foreach chunk-of-dataset do
+                HPCChunkedGradient(...)
+            HPCFinalizeChunkedGradient(Buf, Grad)
+
+        *************************************************************************/
+        public static void hpcpreparechunkedgradient(double[] weights,
+            int wcount,
+            int ntotal,
+            int nin,
+            int nout,
+            mlpbuffers buf,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int batch4size = 0;
+            int chunksize = 0;
+
+            chunksize = 4;
+            batch4size = 3*chunksize*ntotal+chunksize*(2*nout+1);
+            if( alglib.ap.rows(buf.xy)<chunksize || alglib.ap.cols(buf.xy)<nin+nout )
+            {
+                buf.xy = new double[chunksize, nin+nout];
+            }
+            if( alglib.ap.rows(buf.xy2)<chunksize || alglib.ap.cols(buf.xy2)<nin+nout )
+            {
+                buf.xy2 = new double[chunksize, nin+nout];
+            }
+            if( alglib.ap.len(buf.xyrow)<nin+nout )
+            {
+                buf.xyrow = new double[nin+nout];
+            }
+            if( alglib.ap.len(buf.x)<nin )
+            {
+                buf.x = new double[nin];
+            }
+            if( alglib.ap.len(buf.y)<nout )
+            {
+                buf.y = new double[nout];
+            }
+            if( alglib.ap.len(buf.desiredy)<nout )
+            {
+                buf.desiredy = new double[nout];
+            }
+            if( alglib.ap.len(buf.batch4buf)<batch4size )
+            {
+                buf.batch4buf = new double[batch4size];
+            }
+            if( alglib.ap.len(buf.hpcbuf)<wcount )
+            {
+                buf.hpcbuf = new double[wcount];
+            }
+            if( alglib.ap.len(buf.g)<wcount )
+            {
+                buf.g = new double[wcount];
+            }
+            if( !hpcpreparechunkedgradientx(weights, wcount, buf.hpcbuf, _params) )
+            {
+                for(i=0; i<=wcount-1; i++)
+                {
+                    buf.hpcbuf[i] = 0.0;
+                }
+            }
+            buf.wcount = wcount;
+            buf.ntotal = ntotal;
+            buf.nin = nin;
+            buf.nout = nout;
+            buf.chunksize = chunksize;
+        }
+
+
+        /*************************************************************************
+        Finalizes HPC compuations  of  chunked gradient with HPCChunkedGradient().
+        You  have to call this function  after  calling  HPCChunkedGradient()  for
+        a new set of weights. You have to call it only once, see example below:
+
+        HOW TO PROCESS DATASET WITH THIS FUNCTION:
+            Grad:=0
+            HPCPrepareChunkedGradient(Weights, WCount, NTotal, NOut, Buf)
+            foreach chunk-of-dataset do
+                HPCChunkedGradient(...)
+            HPCFinalizeChunkedGradient(Buf, Grad)
+
+        *************************************************************************/
+        public static void hpcfinalizechunkedgradient(mlpbuffers buf,
+            double[] grad,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            if( !hpcfinalizechunkedgradientx(buf.hpcbuf, buf.wcount, grad, _params) )
+            {
+                for(i=0; i<=buf.wcount-1; i++)
+                {
+                    grad[i] = grad[i]+buf.hpcbuf[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Fast kernel for chunked gradient.
+
+        *************************************************************************/
+        public static bool hpcchunkedgradient(double[] weights,
+            int[] structinfo,
+            double[] columnmeans,
+            double[] columnsigmas,
+            double[,] xy,
+            int cstart,
+            int csize,
+            double[] batch4buf,
+            double[] hpcbuf,
+            ref double e,
+            bool naturalerrorfunc,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Fast kernel for chunked processing.
+
+        *************************************************************************/
+        public static bool hpcchunkedprocess(double[] weights,
+            int[] structinfo,
+            double[] columnmeans,
+            double[] columnsigmas,
+            double[,] xy,
+            int cstart,
+            int csize,
+            double[] batch4buf,
+            double[] hpcbuf,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Stub function.
+
+          -- ALGLIB routine --
+             14.06.2013
+             Bochkanov Sergey
+        *************************************************************************/
+        private static bool hpcpreparechunkedgradientx(double[] weights,
+            int wcount,
+            double[] hpcbuf,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Stub function.
+
+          -- ALGLIB routine --
+             14.06.2013
+             Bochkanov Sergey
+        *************************************************************************/
+        private static bool hpcfinalizechunkedgradientx(double[] buf,
+            int wcount,
+            double[] grad,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = false;
+            return result;
+        }
+
+
+    }
+    public class ntheory
+    {
+        public static void findprimitiverootandinverse(int n,
+            ref int proot,
+            ref int invproot,
+            alglib.xparams _params)
+        {
+            int candroot = 0;
+            int phin = 0;
+            int q = 0;
+            int f = 0;
+            bool allnonone = new bool();
+            int x = 0;
+            int lastx = 0;
+            int y = 0;
+            int lasty = 0;
+            int a = 0;
+            int b = 0;
+            int t = 0;
+            int n2 = 0;
+
+            proot = 0;
+            invproot = 0;
+
+            alglib.ap.assert(n>=3, "FindPrimitiveRootAndInverse: N<3");
+            proot = 0;
+            invproot = 0;
+            
+            //
+            // check that N is prime
+            //
+            alglib.ap.assert(isprime(n, _params), "FindPrimitiveRoot: N is not prime");
+            
+            //
+            // Because N is prime, Euler totient function is equal to N-1
+            //
+            phin = n-1;
+            
+            //
+            // Test different values of PRoot - from 2 to N-1.
+            // One of these values MUST be primitive root.
+            //
+            // For testing we use algorithm from Wiki (Primitive root modulo n):
+            // * compute phi(N)
+            // * determine the different prime factors of phi(N), say p1, ..., pk
+            // * for every element m of Zn*, compute m^(phi(N)/pi) mod N for i=1..k
+            //   using a fast algorithm for modular exponentiation.
+            // * a number m for which these k results are all different from 1 is a
+            //   primitive root.
+            //
+            for(candroot=2; candroot<=n-1; candroot++)
+            {
+                
+                //
+                // We have current candidate root in CandRoot.
+                //
+                // Scan different prime factors of PhiN. Here:
+                // * F is a current candidate factor
+                // * Q is a current quotient - amount which was left after dividing PhiN
+                //   by all previous factors
+                //
+                // For each factor, perform test mentioned above.
+                //
+                q = phin;
+                f = 2;
+                allnonone = true;
+                while( q>1 )
+                {
+                    if( q%f==0 )
+                    {
+                        t = modexp(candroot, phin/f, n, _params);
+                        if( t==1 )
+                        {
+                            allnonone = false;
+                            break;
+                        }
+                        while( q%f==0 )
+                        {
+                            q = q/f;
+                        }
+                    }
+                    f = f+1;
+                }
+                if( allnonone )
+                {
+                    proot = candroot;
+                    break;
+                }
+            }
+            alglib.ap.assert(proot>=2, "FindPrimitiveRoot: internal error (root not found)");
+            
+            //
+            // Use extended Euclidean algorithm to find multiplicative inverse of primitive root
+            //
+            x = 0;
+            lastx = 1;
+            y = 1;
+            lasty = 0;
+            a = proot;
+            b = n;
+            while( b!=0 )
+            {
+                q = a/b;
+                t = a%b;
+                a = b;
+                b = t;
+                t = lastx-q*x;
+                lastx = x;
+                x = t;
+                t = lasty-q*y;
+                lasty = y;
+                y = t;
+            }
+            while( lastx<0 )
+            {
+                lastx = lastx+n;
+            }
+            invproot = lastx;
+            
+            //
+            // Check that it is safe to perform multiplication modulo N.
+            // Check results for consistency.
+            //
+            n2 = (n-1)*(n-1);
+            alglib.ap.assert(n2/(n-1)==n-1, "FindPrimitiveRoot: internal error");
+            alglib.ap.assert(proot*invproot/proot==invproot, "FindPrimitiveRoot: internal error");
+            alglib.ap.assert(proot*invproot/invproot==proot, "FindPrimitiveRoot: internal error");
+            alglib.ap.assert(proot*invproot%n==1, "FindPrimitiveRoot: internal error");
+        }
+
+
+        private static bool isprime(int n,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int p = 0;
+
+            result = false;
+            p = 2;
+            while( p*p<=n )
+            {
+                if( n%p==0 )
+                {
+                    return result;
+                }
+                p = p+1;
+            }
+            result = true;
+            return result;
+        }
+
+
+        private static int modmul(int a,
+            int b,
+            int n,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int t = 0;
+            double ra = 0;
+            double rb = 0;
+
+            alglib.ap.assert(a>=0 && a<n, "ModMul: A<0 or A>=N");
+            alglib.ap.assert(b>=0 && b<n, "ModMul: B<0 or B>=N");
+            
+            //
+            // Base cases
+            //
+            ra = a;
+            rb = b;
+            if( b==0 || a==0 )
+            {
+                result = 0;
+                return result;
+            }
+            if( b==1 || a==1 )
+            {
+                result = a*b;
+                return result;
+            }
+            if( (double)(ra*rb)==(double)(a*b) )
+            {
+                result = a*b%n;
+                return result;
+            }
+            
+            //
+            // Non-base cases
+            //
+            if( b%2==0 )
+            {
+                
+                //
+                // A*B = (A*(B/2)) * 2
+                //
+                // Product T=A*(B/2) is calculated recursively, product T*2 is
+                // calculated as follows:
+                // * result:=T-N
+                // * result:=result+T
+                // * if result<0 then result:=result+N
+                //
+                // In case integer result overflows, we generate exception
+                //
+                t = modmul(a, b/2, n, _params);
+                result = t-n;
+                result = result+t;
+                if( result<0 )
+                {
+                    result = result+n;
+                }
+            }
+            else
+            {
+                
+                //
+                // A*B = (A*(B div 2)) * 2 + A
+                //
+                // Product T=A*(B/2) is calculated recursively, product T*2 is
+                // calculated as follows:
+                // * result:=T-N
+                // * result:=result+T
+                // * if result<0 then result:=result+N
+                //
+                // In case integer result overflows, we generate exception
+                //
+                t = modmul(a, b/2, n, _params);
+                result = t-n;
+                result = result+t;
+                if( result<0 )
+                {
+                    result = result+n;
+                }
+                result = result-n;
+                result = result+a;
+                if( result<0 )
+                {
+                    result = result+n;
+                }
+            }
+            return result;
+        }
+
+
+        private static int modexp(int a,
+            int b,
+            int n,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int t = 0;
+
+            alglib.ap.assert(a>=0 && a<n, "ModExp: A<0 or A>=N");
+            alglib.ap.assert(b>=0, "ModExp: B<0");
+            
+            //
+            // Base cases
+            //
+            if( b==0 )
+            {
+                result = 1;
+                return result;
+            }
+            if( b==1 )
+            {
+                result = a;
+                return result;
+            }
+            
+            //
+            // Non-base cases
+            //
+            if( b%2==0 )
+            {
+                t = modmul(a, a, n, _params);
+                result = modexp(t, b/2, n, _params);
+            }
+            else
+            {
+                t = modmul(a, a, n, _params);
+                result = modexp(t, b/2, n, _params);
+                result = modmul(result, a, n, _params);
+            }
+            return result;
+        }
+
+
+    }
+    public class ftbase
+    {
+        /*************************************************************************
+        This record stores execution plan for the fast transformation  along  with
+        preallocated temporary buffers and precalculated values.
+
+        FIELDS:
+            Entries         -   plan entries, one row = one entry (see below for
+                                description).
+            Buf0,Buf1,Buf2  -   global temporary buffers; some of them are allocated,
+                                some of them are not (as decided by plan generation
+                                subroutine).
+            Buffer          -   global buffer whose size is equal to plan size.
+                                There is one-to-one correspondence between elements
+                                of global buffer and elements of array transformed.
+                                Because of it global buffer can be used as temporary
+                                thread-safe storage WITHOUT ACQUIRING LOCK - each
+                                worker thread works with its part of input array,
+                                and each part of input array corresponds to distinct
+                                part of buffer.
+            
+        FORMAT OF THE ENTRIES TABLE:
+
+        Entries table is 2D array which stores one entry per row. Row format is:
+            row[0]      operation type:
+                        *  0 for "end of plan/subplan"
+                        * +1 for "reference O(N^2) complex FFT"
+                        * -1 for complex transposition
+                        * -2 for multiplication by twiddle factors of complex FFT
+                        * -3 for "start of plan/subplan"
+            row[1]      repetition count, >=1
+            row[2]      base operand size (number of microvectors), >=1
+            row[3]      microvector size (measured in real numbers), >=1
+            row[4]      parameter0, meaning depends on row[0]
+            row[5]      parameter1, meaning depends on row[0]
+
+        FORMAT OF THE DATA:
+
+        Transformation plan works with row[1]*row[2]*row[3]  real  numbers,  which
+        are (in most cases) interpreted as sequence of complex numbers. These data
+        are grouped as follows:
+        * we have row[1] contiguous OPERANDS, which can be treated separately
+        * each operand includes row[2] contiguous MICROVECTORS
+        * each microvector includes row[3] COMPONENTS, which can be treated separately
+        * pair of components form complex number, so in most cases row[3] will be even
+
+        Say, if you want to perform complex FFT of length 3, then:
+        * you have 1 operand: row[1]=1
+        * operand consists of 3 microvectors:   row[2]=3
+        * each microvector has two components:  row[3]=2
+        * a pair of subsequent components is treated as complex number
+
+        if you want to perform TWO simultaneous complex FFT's of length 3, then you
+        can choose between two representations:
+        * 1 operand, 3 microvectors, 4 components; storage format is given below:
+          [ A0X A0Y B0X B0Y A1X A1Y B1X B1Y ... ]
+          (here A denotes first sequence, B - second one).
+        * 2 operands, 3 microvectors, 2 components; storage format is given below:
+          [ A0X A0Y A1X A2Y ... B0X B0Y B1X B1Y ... ]
+        Most FFT operations are supported only for the second format, but you
+        should remember that first format sometimes can be used too.
+
+        SUPPORTED OPERATIONS:
+
+        row[0]=0:
+        * "end of plan/subplan"
+        * in case we meet entry with such type,  FFT  transformation  is  finished
+          (or we return from recursive FFT subplan, in case it was subplan).
+
+        row[0]=+1:
+        * "reference 1D complex FFT"
+        * we perform reference O(N^2) complex FFT on input data, which are treated
+          as row[1] arrays, each of row[2] complex numbers, and row[3] must be
+          equal to 2
+        * transformation is performed using temporary buffer
+
+        row[0]=opBluesteinsFFT:
+        * input array is handled with Bluestein's algorithm (by zero-padding to
+          Param0 complex numbers).
+        * this plan calls Param0-point subplan which is located at offset Param1
+          (offset is measured with respect to location of the calling entry)
+        * this plan uses precomputed quantities stored in Plan.PrecR at
+          offset Param2.
+        * transformation is performed using 4 temporary buffers, which are
+          retrieved from Plan.BluesteinPool.
+
+        row[0]=+3:
+        * "optimized 1D complex FFT"
+        * this function supports only several operand sizes: from 1 to 5.
+          These transforms are hard-coded and performed very efficiently
+
+        row[0]=opRadersFFT:
+        * input array is handled with Rader's algorithm (permutation and
+          reduction to N-1-point FFT)
+        * this plan calls N-1-point subplan which is located at offset Param0
+          (offset is measured with respect to location of the calling entry)
+        * this plan uses precomputed primitive root and its inverse (modulo N)
+          which are stored in Param1 and Param2.
+        * Param3 stores offset of the precomputed data for the plan
+        * plan length must be prime, (N-1)*(N-1) must fit into integer variable
+
+        row[0]=-1
+        * "complex transposition"
+        * input data are treated as row[1] independent arrays, which are processed
+          separately
+        * each of operands is treated as matrix with row[4] rows and row[2]/row[4]
+          columns. Each element of the matrix is microvector with row[3] components.
+        * transposition is performed using temporary buffer
+
+        row[0]=-2
+        * "multiplication by twiddle factors of complex FFT"
+        * input data are treated as row[1] independent arrays, which are processed
+          separately
+        * row[4] contains N1 - length of the "first FFT"  in  a  Cooley-Tukey  FFT
+          algorithm
+        * this function does not require temporary buffers
+
+        row[0]=-3
+        * "start of the plan"
+        * each subplan must start from this entry
+        * param0 is ignored
+        * param1 stores approximate (optimistic) estimate of KFLOPs required to
+          transform one operand of the plan. Total cost of the plan is approximately
+          equal to row[1]*param1 KFLOPs.
+        * this function does not require temporary buffers
+
+        row[0]=-4
+        * "jump"
+        * param0 stores relative offset of the jump site
+          (+1 corresponds to the next entry)
+
+        row[0]=-5
+        * "parallel call"
+        * input data are treated as row[1] independent arrays
+        * child subplan is applied independently for each of arrays - row[1] times
+        * subplan length must be equal to row[2]*row[3]
+        * param0 stores relative offset of the child subplan site
+          (+1 corresponds to the next entry)
+        * param1 stores approximate total cost of plan, measured in UNITS
+          (1 UNIT = 100 KFLOPs). Plan cost must be rounded DOWN to nearest integer.
+
+
+            
+        TODO 
+             2. from KFLOPs to UNITs, 1 UNIT = 100 000 FLOP!!!!!!!!!!!
+
+             3. from IsRoot to TaskType = {0, -1, +1}; or maybe, add IsSeparatePlan
+                to distinguish root of child subplan from global root which uses
+                separate buffer
+                
+             4. child subplans in parallel call must NOT use buffer provided by parent plan;
+                they must allocate their own local buffer
+        *************************************************************************/
+        public class fasttransformplan : apobject
+        {
+            public int[,] entries;
+            public double[] buffer;
+            public double[] precr;
+            public double[] preci;
+            public alglib.smp.shared_pool bluesteinpool;
+            public fasttransformplan()
+            {
+                init();
+            }
+            public override void init()
+            {
+                entries = new int[0,0];
+                buffer = new double[0];
+                precr = new double[0];
+                preci = new double[0];
+                bluesteinpool = new alglib.smp.shared_pool();
+            }
+            public override alglib.apobject make_copy()
+            {
+                fasttransformplan _result = new fasttransformplan();
+                _result.entries = (int[,])entries.Clone();
+                _result.buffer = (double[])buffer.Clone();
+                _result.precr = (double[])precr.Clone();
+                _result.preci = (double[])preci.Clone();
+                _result.bluesteinpool = (alglib.smp.shared_pool)bluesteinpool.make_copy();
+                return _result;
+            }
+        };
+
+
+
+
+        public const int coltype = 0;
+        public const int coloperandscnt = 1;
+        public const int coloperandsize = 2;
+        public const int colmicrovectorsize = 3;
+        public const int colparam0 = 4;
+        public const int colparam1 = 5;
+        public const int colparam2 = 6;
+        public const int colparam3 = 7;
+        public const int colscnt = 8;
+        public const int opend = 0;
+        public const int opcomplexreffft = 1;
+        public const int opbluesteinsfft = 2;
+        public const int opcomplexcodeletfft = 3;
+        public const int opcomplexcodelettwfft = 4;
+        public const int opradersfft = 5;
+        public const int opcomplextranspose = -1;
+        public const int opcomplexfftfactors = -2;
+        public const int opstart = -3;
+        public const int opjmp = -4;
+        public const int opparallelcall = -5;
+        public const int maxradix = 6;
+        public const int updatetw = 16;
+        public const int recursivethreshold = 1024;
+        public const int raderthreshold = 19;
+        public const int ftbasecodeletrecommended = 5;
+        public const double ftbaseinefficiencyfactor = 1.3;
+        public const int ftbasemaxsmoothfactor = 5;
+
+
+        /*************************************************************************
+        This subroutine generates FFT plan for K complex FFT's with length N each.
+
+        INPUT PARAMETERS:
+            N           -   FFT length (in complex numbers), N>=1
+            K           -   number of repetitions, K>=1
+            
+        OUTPUT PARAMETERS:
+            Plan        -   plan
+
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ftcomplexfftplan(int n,
+            int k,
+            fasttransformplan plan,
+            alglib.xparams _params)
+        {
+            apserv.srealarray bluesteinbuf = new apserv.srealarray();
+            int rowptr = 0;
+            int bluesteinsize = 0;
+            int precrptr = 0;
+            int preciptr = 0;
+            int precrsize = 0;
+            int precisize = 0;
+
+            
+            //
+            // Initial check for parameters
+            //
+            alglib.ap.assert(n>0, "FTComplexFFTPlan: N<=0");
+            alglib.ap.assert(k>0, "FTComplexFFTPlan: K<=0");
+            
+            //
+            // Determine required sizes of precomputed real and integer
+            // buffers. This stage of code is highly dependent on internals
+            // of FTComplexFFTPlanRec() and must be kept synchronized with
+            // possible changes in internals of plan generation function.
+            //
+            // Buffer size is determined as follows:
+            // * N is factorized
+            // * we factor out anything which is less or equal to MaxRadix
+            // * prime factor F>RaderThreshold requires 4*FTBaseFindSmooth(2*F-1)
+            //   real entries to store precomputed Quantities for Bluestein's
+            //   transformation
+            // * prime factor F<=RaderThreshold does NOT require
+            //   precomputed storage
+            //
+            precrsize = 0;
+            precisize = 0;
+            ftdeterminespacerequirements(n, ref precrsize, ref precisize, _params);
+            if( precrsize>0 )
+            {
+                plan.precr = new double[precrsize];
+            }
+            if( precisize>0 )
+            {
+                plan.preci = new double[precisize];
+            }
+            
+            //
+            // Generate plan
+            //
+            rowptr = 0;
+            precrptr = 0;
+            preciptr = 0;
+            bluesteinsize = 1;
+            plan.buffer = new double[2*n*k];
+            ftcomplexfftplanrec(n, k, true, true, ref rowptr, ref bluesteinsize, ref precrptr, ref preciptr, plan, _params);
+            bluesteinbuf.val = new double[bluesteinsize];
+            alglib.smp.ae_shared_pool_set_seed(plan.bluesteinpool, bluesteinbuf);
+            
+            //
+            // Check that actual amount of precomputed space used by transformation
+            // plan is EXACTLY equal to amount of space allocated by us.
+            //
+            alglib.ap.assert(precrptr==precrsize, "FTComplexFFTPlan: internal error (PrecRPtr<>PrecRSize)");
+            alglib.ap.assert(preciptr==precisize, "FTComplexFFTPlan: internal error (PrecRPtr<>PrecRSize)");
+        }
+
+
+        /*************************************************************************
+        This subroutine applies transformation plan to input/output array A.
+
+        INPUT PARAMETERS:
+            Plan        -   transformation plan
+            A           -   array, must be large enough for plan to work
+            OffsA       -   offset of the subarray to process
+            RepCnt      -   repetition count (transformation is repeatedly applied
+                            to subsequent subarrays)
+            
+        OUTPUT PARAMETERS:
+            Plan        -   plan (temporary buffers can be modified, plan itself
+                            is unchanged and can be reused)
+            A           -   transformed array
+
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ftapplyplan(fasttransformplan plan,
+            double[] a,
+            int offsa,
+            int repcnt,
+            alglib.xparams _params)
+        {
+            int plansize = 0;
+            int i = 0;
+
+            plansize = plan.entries[0,coloperandscnt]*plan.entries[0,coloperandsize]*plan.entries[0,colmicrovectorsize];
+            for(i=0; i<=repcnt-1; i++)
+            {
+                ftapplysubplan(plan, 0, a, offsa+plansize*i, 0, plan.buffer, 1, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int best = 0;
+
+            best = 2;
+            while( best<n )
+            {
+                best = 2*best;
+            }
+            ftbasefindsmoothrec(n, 1, 2, ref best, _params);
+            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,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int best = 0;
+
+            best = 2;
+            while( best<n )
+            {
+                best = 2*best;
+            }
+            ftbasefindsmoothrec(n, 2, 2, ref best, _params);
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = ftbaseinefficiencyfactor*(4*n*Math.Log(n)/Math.Log(2)-6*n+8);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns EXACT estimate of the space requirements for N-point
+        FFT. Internals of this function are highly dependent on details of different
+        FFTs employed by this unit, so every time algorithm is changed this function
+        has to be rewritten.
+
+        INPUT PARAMETERS:
+            N           -   transform length
+            PrecRSize   -   must be set to zero
+            PrecISize   -   must be set to zero
+            
+        OUTPUT PARAMETERS:
+            PrecRSize   -   number of real temporaries required for transformation
+            PrecISize   -   number of integer temporaries required for transformation
+
+            
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftdeterminespacerequirements(int n,
+            ref int precrsize,
+            ref int precisize,
+            alglib.xparams _params)
+        {
+            int ncur = 0;
+            int f = 0;
+            int i = 0;
+
+            
+            //
+            // Determine required sizes of precomputed real and integer
+            // buffers. This stage of code is highly dependent on internals
+            // of FTComplexFFTPlanRec() and must be kept synchronized with
+            // possible changes in internals of plan generation function.
+            //
+            // Buffer size is determined as follows:
+            // * N is factorized
+            // * we factor out anything which is less or equal to MaxRadix
+            // * prime factor F>RaderThreshold requires 4*FTBaseFindSmooth(2*F-1)
+            //   real entries to store precomputed Quantities for Bluestein's
+            //   transformation
+            // * prime factor F<=RaderThreshold requires 2*(F-1)+ESTIMATE(F-1)
+            //   precomputed storage
+            //
+            ncur = n;
+            for(i=2; i<=maxradix; i++)
+            {
+                while( ncur%i==0 )
+                {
+                    ncur = ncur/i;
+                }
+            }
+            f = 2;
+            while( f<=ncur )
+            {
+                while( ncur%f==0 )
+                {
+                    if( f>raderthreshold )
+                    {
+                        precrsize = precrsize+4*ftbasefindsmooth(2*f-1, _params);
+                    }
+                    else
+                    {
+                        precrsize = precrsize+2*(f-1);
+                        ftdeterminespacerequirements(f-1, ref precrsize, ref precisize, _params);
+                    }
+                    ncur = ncur/f;
+                }
+                f = f+1;
+            }
+        }
+
+
+        /*************************************************************************
+        Recurrent function called by FTComplexFFTPlan() and other functions. It
+        recursively builds transformation plan
+
+        INPUT PARAMETERS:
+            N           -   FFT length (in complex numbers), N>=1
+            K           -   number of repetitions, K>=1
+            ChildPlan   -   if True, plan generator inserts OpStart/opEnd in the
+                            plan header/footer.
+            TopmostPlan -   if True, plan generator assumes that it is topmost plan:
+                            * it may use global buffer for transpositions
+                            and there is no other plan which executes in parallel
+            RowPtr      -   index which points to past-the-last entry generated so far
+            BluesteinSize-  amount of storage (in real numbers) required for Bluestein buffer
+            PrecRPtr    -   pointer to unused part of precomputed real buffer (Plan.PrecR):
+                            * when this function stores some data to precomputed buffer,
+                              it advances pointer.
+                            * it is responsibility of the function to assert that
+                              Plan.PrecR has enough space to store data before actually
+                              writing to buffer.
+                            * it is responsibility of the caller to allocate enough
+                              space before calling this function
+            PrecIPtr    -   pointer to unused part of precomputed integer buffer (Plan.PrecI):
+                            * when this function stores some data to precomputed buffer,
+                              it advances pointer.
+                            * it is responsibility of the function to assert that
+                              Plan.PrecR has enough space to store data before actually
+                              writing to buffer.
+                            * it is responsibility of the caller to allocate enough
+                              space before calling this function
+            Plan        -   plan (generated so far)
+            
+        OUTPUT PARAMETERS:
+            RowPtr      -   updated pointer (advanced by number of entries generated
+                            by function)
+            BluesteinSize-  updated amount
+                            (may be increased, but may never be decreased)
+                
+        NOTE: in case TopmostPlan is True, ChildPlan is also must be True.
+            
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftcomplexfftplanrec(int n,
+            int k,
+            bool childplan,
+            bool topmostplan,
+            ref int rowptr,
+            ref int bluesteinsize,
+            ref int precrptr,
+            ref int preciptr,
+            fasttransformplan plan,
+            alglib.xparams _params)
+        {
+            apserv.srealarray localbuf = new apserv.srealarray();
+            int m = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int gq = 0;
+            int giq = 0;
+            int row0 = 0;
+            int row1 = 0;
+            int row2 = 0;
+            int row3 = 0;
+
+            alglib.ap.assert(n>0, "FTComplexFFTPlan: N<=0");
+            alglib.ap.assert(k>0, "FTComplexFFTPlan: K<=0");
+            alglib.ap.assert(!topmostplan || childplan, "FTComplexFFTPlan: ChildPlan is inconsistent with TopmostPlan");
+            
+            //
+            // Try to generate "topmost" plan
+            //
+            if( topmostplan && n>recursivethreshold )
+            {
+                ftfactorize(n, false, ref n1, ref n2, _params);
+                if( n1*n2==0 )
+                {
+                    
+                    //
+                    // Handle prime-factor FFT with Bluestein's FFT.
+                    // Determine size of Bluestein's buffer.
+                    //
+                    m = ftbasefindsmooth(2*n-1, _params);
+                    bluesteinsize = Math.Max(2*m, bluesteinsize);
+                    
+                    //
+                    // Generate plan
+                    //
+                    ftpushentry2(plan, ref rowptr, opstart, k, n, 2, -1, ftoptimisticestimate(n, _params), _params);
+                    ftpushentry4(plan, ref rowptr, opbluesteinsfft, k, n, 2, m, 2, precrptr, 0, _params);
+                    row0 = rowptr;
+                    ftpushentry(plan, ref rowptr, opjmp, 0, 0, 0, 0, _params);
+                    ftcomplexfftplanrec(m, 1, true, true, ref rowptr, ref bluesteinsize, ref precrptr, ref preciptr, plan, _params);
+                    row1 = rowptr;
+                    plan.entries[row0,colparam0] = row1-row0;
+                    ftpushentry(plan, ref rowptr, opend, k, n, 2, 0, _params);
+                    
+                    //
+                    // Fill precomputed buffer
+                    //
+                    ftprecomputebluesteinsfft(n, m, plan.precr, precrptr, _params);
+                    
+                    //
+                    // Update pointer to the precomputed area
+                    //
+                    precrptr = precrptr+4*m;
+                }
+                else
+                {
+                    
+                    //
+                    // Handle composite FFT with recursive Cooley-Tukey which
+                    // uses global buffer instead of local one.
+                    //
+                    ftpushentry2(plan, ref rowptr, opstart, k, n, 2, -1, ftoptimisticestimate(n, _params), _params);
+                    ftpushentry(plan, ref rowptr, opcomplextranspose, k, n, 2, n1, _params);
+                    row0 = rowptr;
+                    ftpushentry2(plan, ref rowptr, opparallelcall, k*n2, n1, 2, 0, ftoptimisticestimate(n, _params), _params);
+                    ftpushentry(plan, ref rowptr, opcomplexfftfactors, k, n, 2, n1, _params);
+                    ftpushentry(plan, ref rowptr, opcomplextranspose, k, n, 2, n2, _params);
+                    row2 = rowptr;
+                    ftpushentry2(plan, ref rowptr, opparallelcall, k*n1, n2, 2, 0, ftoptimisticestimate(n, _params), _params);
+                    ftpushentry(plan, ref rowptr, opcomplextranspose, k, n, 2, n1, _params);
+                    ftpushentry(plan, ref rowptr, opend, k, n, 2, 0, _params);
+                    row1 = rowptr;
+                    ftcomplexfftplanrec(n1, 1, true, false, ref rowptr, ref bluesteinsize, ref precrptr, ref preciptr, plan, _params);
+                    plan.entries[row0,colparam0] = row1-row0;
+                    row3 = rowptr;
+                    ftcomplexfftplanrec(n2, 1, true, false, ref rowptr, ref bluesteinsize, ref precrptr, ref preciptr, plan, _params);
+                    plan.entries[row2,colparam0] = row3-row2;
+                }
+                return;
+            }
+            
+            //
+            // Prepare "non-topmost" plan:
+            // * calculate factorization
+            // * use local (shared) buffer
+            // * update buffer size - ANY plan will need at least
+            //   2*N temporaries, additional requirements can be
+            //   applied later
+            //
+            ftfactorize(n, false, ref n1, ref n2, _params);
+            
+            //
+            // Handle FFT's with N1*N2=0: either small-N or prime-factor
+            //
+            if( n1*n2==0 )
+            {
+                if( n<=maxradix )
+                {
+                    
+                    //
+                    // Small-N FFT
+                    //
+                    if( childplan )
+                    {
+                        ftpushentry2(plan, ref rowptr, opstart, k, n, 2, -1, ftoptimisticestimate(n, _params), _params);
+                    }
+                    ftpushentry(plan, ref rowptr, opcomplexcodeletfft, k, n, 2, 0, _params);
+                    if( childplan )
+                    {
+                        ftpushentry(plan, ref rowptr, opend, k, n, 2, 0, _params);
+                    }
+                    return;
+                }
+                if( n<=raderthreshold )
+                {
+                    
+                    //
+                    // Handle prime-factor FFT's with Rader's FFT
+                    //
+                    m = n-1;
+                    if( childplan )
+                    {
+                        ftpushentry2(plan, ref rowptr, opstart, k, n, 2, -1, ftoptimisticestimate(n, _params), _params);
+                    }
+                    ntheory.findprimitiverootandinverse(n, ref gq, ref giq, _params);
+                    ftpushentry4(plan, ref rowptr, opradersfft, k, n, 2, 2, gq, giq, precrptr, _params);
+                    ftprecomputeradersfft(n, gq, giq, plan.precr, precrptr, _params);
+                    precrptr = precrptr+2*(n-1);
+                    row0 = rowptr;
+                    ftpushentry(plan, ref rowptr, opjmp, 0, 0, 0, 0, _params);
+                    ftcomplexfftplanrec(m, 1, true, false, ref rowptr, ref bluesteinsize, ref precrptr, ref preciptr, plan, _params);
+                    row1 = rowptr;
+                    plan.entries[row0,colparam0] = row1-row0;
+                    if( childplan )
+                    {
+                        ftpushentry(plan, ref rowptr, opend, k, n, 2, 0, _params);
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Handle prime-factor FFT's with Bluestein's FFT
+                    //
+                    m = ftbasefindsmooth(2*n-1, _params);
+                    bluesteinsize = Math.Max(2*m, bluesteinsize);
+                    if( childplan )
+                    {
+                        ftpushentry2(plan, ref rowptr, opstart, k, n, 2, -1, ftoptimisticestimate(n, _params), _params);
+                    }
+                    ftpushentry4(plan, ref rowptr, opbluesteinsfft, k, n, 2, m, 2, precrptr, 0, _params);
+                    ftprecomputebluesteinsfft(n, m, plan.precr, precrptr, _params);
+                    precrptr = precrptr+4*m;
+                    row0 = rowptr;
+                    ftpushentry(plan, ref rowptr, opjmp, 0, 0, 0, 0, _params);
+                    ftcomplexfftplanrec(m, 1, true, false, ref rowptr, ref bluesteinsize, ref precrptr, ref preciptr, plan, _params);
+                    row1 = rowptr;
+                    plan.entries[row0,colparam0] = row1-row0;
+                    if( childplan )
+                    {
+                        ftpushentry(plan, ref rowptr, opend, k, n, 2, 0, _params);
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Handle Cooley-Tukey FFT with small N1
+            //
+            if( n1<=maxradix )
+            {
+                
+                //
+                // Specialized transformation for small N1:
+                // * N2 short inplace FFT's, each N1-point, with integrated twiddle factors
+                // * N1 long FFT's
+                // * final transposition
+                //
+                if( childplan )
+                {
+                    ftpushentry2(plan, ref rowptr, opstart, k, n, 2, -1, ftoptimisticestimate(n, _params), _params);
+                }
+                ftpushentry(plan, ref rowptr, opcomplexcodelettwfft, k, n1, 2*n2, 0, _params);
+                ftcomplexfftplanrec(n2, k*n1, false, false, ref rowptr, ref bluesteinsize, ref precrptr, ref preciptr, plan, _params);
+                ftpushentry(plan, ref rowptr, opcomplextranspose, k, n, 2, n1, _params);
+                if( childplan )
+                {
+                    ftpushentry(plan, ref rowptr, opend, k, n, 2, 0, _params);
+                }
+                return;
+            }
+            
+            //
+            // Handle general Cooley-Tukey FFT, either "flat" or "recursive"
+            //
+            if( n<=recursivethreshold )
+            {
+                
+                //
+                // General code for large N1/N2, "flat" version without explicit recurrence
+                // (nested subplans are inserted directly into the body of the plan)
+                //
+                if( childplan )
+                {
+                    ftpushentry2(plan, ref rowptr, opstart, k, n, 2, -1, ftoptimisticestimate(n, _params), _params);
+                }
+                ftpushentry(plan, ref rowptr, opcomplextranspose, k, n, 2, n1, _params);
+                ftcomplexfftplanrec(n1, k*n2, false, false, ref rowptr, ref bluesteinsize, ref precrptr, ref preciptr, plan, _params);
+                ftpushentry(plan, ref rowptr, opcomplexfftfactors, k, n, 2, n1, _params);
+                ftpushentry(plan, ref rowptr, opcomplextranspose, k, n, 2, n2, _params);
+                ftcomplexfftplanrec(n2, k*n1, false, false, ref rowptr, ref bluesteinsize, ref precrptr, ref preciptr, plan, _params);
+                ftpushentry(plan, ref rowptr, opcomplextranspose, k, n, 2, n1, _params);
+                if( childplan )
+                {
+                    ftpushentry(plan, ref rowptr, opend, k, n, 2, 0, _params);
+                }
+            }
+            else
+            {
+                
+                //
+                // General code for large N1/N2, "recursive" version - nested subplans
+                // are separated from the plan body.
+                //
+                // Generate parent plan.
+                //
+                if( childplan )
+                {
+                    ftpushentry2(plan, ref rowptr, opstart, k, n, 2, -1, ftoptimisticestimate(n, _params), _params);
+                }
+                ftpushentry(plan, ref rowptr, opcomplextranspose, k, n, 2, n1, _params);
+                row0 = rowptr;
+                ftpushentry2(plan, ref rowptr, opparallelcall, k*n2, n1, 2, 0, ftoptimisticestimate(n, _params), _params);
+                ftpushentry(plan, ref rowptr, opcomplexfftfactors, k, n, 2, n1, _params);
+                ftpushentry(plan, ref rowptr, opcomplextranspose, k, n, 2, n2, _params);
+                row2 = rowptr;
+                ftpushentry2(plan, ref rowptr, opparallelcall, k*n1, n2, 2, 0, ftoptimisticestimate(n, _params), _params);
+                ftpushentry(plan, ref rowptr, opcomplextranspose, k, n, 2, n1, _params);
+                if( childplan )
+                {
+                    ftpushentry(plan, ref rowptr, opend, k, n, 2, 0, _params);
+                }
+                
+                //
+                // Generate child subplans, insert refence to parent plans
+                //
+                row1 = rowptr;
+                ftcomplexfftplanrec(n1, 1, true, false, ref rowptr, ref bluesteinsize, ref precrptr, ref preciptr, plan, _params);
+                plan.entries[row0,colparam0] = row1-row0;
+                row3 = rowptr;
+                ftcomplexfftplanrec(n2, 1, true, false, ref rowptr, ref bluesteinsize, ref precrptr, ref preciptr, plan, _params);
+                plan.entries[row2,colparam0] = row3-row2;
+            }
+        }
+
+
+        /*************************************************************************
+        This function pushes one more entry to the plan. It resizes Entries matrix
+        if needed.
+
+        INPUT PARAMETERS:
+            Plan        -   plan (generated so far)
+            RowPtr      -   index which points to past-the-last entry generated so far
+            EType       -   entry type
+            EOpCnt      -   operands count
+            EOpSize     -   operand size
+            EMcvSize    -   microvector size
+            EParam0     -   parameter 0
+            
+        OUTPUT PARAMETERS:
+            Plan        -   updated plan    
+            RowPtr      -   updated pointer
+
+        NOTE: Param1 is set to -1.
+            
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftpushentry(fasttransformplan plan,
+            ref int rowptr,
+            int etype,
+            int eopcnt,
+            int eopsize,
+            int emcvsize,
+            int eparam0,
+            alglib.xparams _params)
+        {
+            ftpushentry2(plan, ref rowptr, etype, eopcnt, eopsize, emcvsize, eparam0, -1, _params);
+        }
+
+
+        /*************************************************************************
+        Same as FTPushEntry(), but sets Param0 AND Param1.
+        This function pushes one more entry to the plan. It resized Entries matrix
+        if needed.
+
+        INPUT PARAMETERS:
+            Plan        -   plan (generated so far)
+            RowPtr      -   index which points to past-the-last entry generated so far
+            EType       -   entry type
+            EOpCnt      -   operands count
+            EOpSize     -   operand size
+            EMcvSize    -   microvector size
+            EParam0     -   parameter 0
+            EParam1     -   parameter 1
+            
+        OUTPUT PARAMETERS:
+            Plan        -   updated plan    
+            RowPtr      -   updated pointer
+
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftpushentry2(fasttransformplan plan,
+            ref int rowptr,
+            int etype,
+            int eopcnt,
+            int eopsize,
+            int emcvsize,
+            int eparam0,
+            int eparam1,
+            alglib.xparams _params)
+        {
+            if( rowptr>=alglib.ap.rows(plan.entries) )
+            {
+                apserv.imatrixresize(ref plan.entries, Math.Max(2*alglib.ap.rows(plan.entries), 1), colscnt, _params);
+            }
+            plan.entries[rowptr,coltype] = etype;
+            plan.entries[rowptr,coloperandscnt] = eopcnt;
+            plan.entries[rowptr,coloperandsize] = eopsize;
+            plan.entries[rowptr,colmicrovectorsize] = emcvsize;
+            plan.entries[rowptr,colparam0] = eparam0;
+            plan.entries[rowptr,colparam1] = eparam1;
+            plan.entries[rowptr,colparam2] = 0;
+            plan.entries[rowptr,colparam3] = 0;
+            rowptr = rowptr+1;
+        }
+
+
+        /*************************************************************************
+        Same as FTPushEntry(), but sets Param0, Param1, Param2 and Param3.
+        This function pushes one more entry to the plan. It resized Entries matrix
+        if needed.
+
+        INPUT PARAMETERS:
+            Plan        -   plan (generated so far)
+            RowPtr      -   index which points to past-the-last entry generated so far
+            EType       -   entry type
+            EOpCnt      -   operands count
+            EOpSize     -   operand size
+            EMcvSize    -   microvector size
+            EParam0     -   parameter 0
+            EParam1     -   parameter 1
+            EParam2     -   parameter 2
+            EParam3     -   parameter 3
+            
+        OUTPUT PARAMETERS:
+            Plan        -   updated plan    
+            RowPtr      -   updated pointer
+
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftpushentry4(fasttransformplan plan,
+            ref int rowptr,
+            int etype,
+            int eopcnt,
+            int eopsize,
+            int emcvsize,
+            int eparam0,
+            int eparam1,
+            int eparam2,
+            int eparam3,
+            alglib.xparams _params)
+        {
+            if( rowptr>=alglib.ap.rows(plan.entries) )
+            {
+                apserv.imatrixresize(ref plan.entries, Math.Max(2*alglib.ap.rows(plan.entries), 1), colscnt, _params);
+            }
+            plan.entries[rowptr,coltype] = etype;
+            plan.entries[rowptr,coloperandscnt] = eopcnt;
+            plan.entries[rowptr,coloperandsize] = eopsize;
+            plan.entries[rowptr,colmicrovectorsize] = emcvsize;
+            plan.entries[rowptr,colparam0] = eparam0;
+            plan.entries[rowptr,colparam1] = eparam1;
+            plan.entries[rowptr,colparam2] = eparam2;
+            plan.entries[rowptr,colparam3] = eparam3;
+            rowptr = rowptr+1;
+        }
+
+
+        /*************************************************************************
+        This subroutine applies subplan to input/output array A.
+
+        INPUT PARAMETERS:
+            Plan        -   transformation plan
+            SubPlan     -   subplan index
+            A           -   array, must be large enough for plan to work
+            ABase       -   base offset in array A, this value points to start of
+                            subarray whose length is equal to length of the plan
+            AOffset     -   offset with respect to ABase, 0<=AOffset<PlanLength.
+                            This is an offset within large PlanLength-subarray of
+                            the chunk to process.
+            Buf         -   temporary buffer whose length is equal to plan length
+                            (without taking into account RepCnt) or larger.
+            OffsBuf     -   offset in the buffer array
+            RepCnt      -   repetition count (transformation is repeatedly applied
+                            to subsequent subarrays)
+            
+        OUTPUT PARAMETERS:
+            Plan        -   plan (temporary buffers can be modified, plan itself
+                            is unchanged and can be reused)
+            A           -   transformed array
+
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftapplysubplan(fasttransformplan plan,
+            int subplan,
+            double[] a,
+            int abase,
+            int aoffset,
+            double[] buf,
+            int repcnt,
+            alglib.xparams _params)
+        {
+            int rowidx = 0;
+            int i = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int operation = 0;
+            int operandscnt = 0;
+            int operandsize = 0;
+            int microvectorsize = 0;
+            int param0 = 0;
+            int param1 = 0;
+            int parentsize = 0;
+            int childsize = 0;
+            int chunksize = 0;
+            int lastchunksize = 0;
+            apserv.srealarray bufa = null;
+            apserv.srealarray bufb = null;
+            apserv.srealarray bufc = null;
+            apserv.srealarray bufd = null;
+
+            alglib.ap.assert(plan.entries[subplan,coltype]==opstart, "FTApplySubPlan: incorrect subplan header");
+            rowidx = subplan+1;
+            while( plan.entries[rowidx,coltype]!=opend )
+            {
+                operation = plan.entries[rowidx,coltype];
+                operandscnt = repcnt*plan.entries[rowidx,coloperandscnt];
+                operandsize = plan.entries[rowidx,coloperandsize];
+                microvectorsize = plan.entries[rowidx,colmicrovectorsize];
+                param0 = plan.entries[rowidx,colparam0];
+                param1 = plan.entries[rowidx,colparam1];
+                apserv.touchint(ref param1, _params);
+                
+                //
+                // Process "jump" operation
+                //
+                if( operation==opjmp )
+                {
+                    rowidx = rowidx+plan.entries[rowidx,colparam0];
+                    continue;
+                }
+                
+                //
+                // Process "parallel call" operation:
+                // * we perform initial check for consistency between parent and child plans
+                // * we call FTSplitAndApplyParallelPlan(), which splits parallel plan into
+                //   several parallel tasks
+                //
+                if( operation==opparallelcall )
+                {
+                    parentsize = operandsize*microvectorsize;
+                    childsize = plan.entries[rowidx+param0,coloperandscnt]*plan.entries[rowidx+param0,coloperandsize]*plan.entries[rowidx+param0,colmicrovectorsize];
+                    alglib.ap.assert(plan.entries[rowidx+param0,coltype]==opstart, "FTApplySubPlan: incorrect child subplan header");
+                    alglib.ap.assert(parentsize==childsize, "FTApplySubPlan: incorrect child subplan header");
+                    chunksize = Math.Max(recursivethreshold/childsize, 1);
+                    lastchunksize = operandscnt%chunksize;
+                    if( lastchunksize==0 )
+                    {
+                        lastchunksize = chunksize;
+                    }
+                    i = 0;
+                    while( i<operandscnt )
+                    {
+                        chunksize = Math.Min(chunksize, operandscnt-i);
+                        ftapplysubplan(plan, rowidx+param0, a, abase, aoffset+i*childsize, buf, chunksize, _params);
+                        i = i+chunksize;
+                    }
+                    rowidx = rowidx+1;
+                    continue;
+                }
+                
+                //
+                // Process "reference complex FFT" operation
+                //
+                if( operation==opcomplexreffft )
+                {
+                    ftapplycomplexreffft(a, abase+aoffset, operandscnt, operandsize, microvectorsize, buf, _params);
+                    rowidx = rowidx+1;
+                    continue;
+                }
+                
+                //
+                // Process "codelet FFT" operation
+                //
+                if( operation==opcomplexcodeletfft )
+                {
+                    ftapplycomplexcodeletfft(a, abase+aoffset, operandscnt, operandsize, microvectorsize, _params);
+                    rowidx = rowidx+1;
+                    continue;
+                }
+                
+                //
+                // Process "integrated codelet FFT" operation
+                //
+                if( operation==opcomplexcodelettwfft )
+                {
+                    ftapplycomplexcodelettwfft(a, abase+aoffset, operandscnt, operandsize, microvectorsize, _params);
+                    rowidx = rowidx+1;
+                    continue;
+                }
+                
+                //
+                // Process Bluestein's FFT operation
+                //
+                if( operation==opbluesteinsfft )
+                {
+                    alglib.ap.assert(microvectorsize==2, "FTApplySubPlan: microvectorsize!=2 for Bluesteins FFT");
+                    alglib.smp.ae_shared_pool_retrieve(plan.bluesteinpool, ref bufa);
+                    alglib.smp.ae_shared_pool_retrieve(plan.bluesteinpool, ref bufb);
+                    alglib.smp.ae_shared_pool_retrieve(plan.bluesteinpool, ref bufc);
+                    alglib.smp.ae_shared_pool_retrieve(plan.bluesteinpool, ref bufd);
+                    ftbluesteinsfft(plan, a, abase, aoffset, operandscnt, operandsize, plan.entries[rowidx,colparam0], plan.entries[rowidx,colparam2], rowidx+plan.entries[rowidx,colparam1], bufa.val, bufb.val, bufc.val, bufd.val, _params);
+                    alglib.smp.ae_shared_pool_recycle(plan.bluesteinpool, ref bufa);
+                    alglib.smp.ae_shared_pool_recycle(plan.bluesteinpool, ref bufb);
+                    alglib.smp.ae_shared_pool_recycle(plan.bluesteinpool, ref bufc);
+                    alglib.smp.ae_shared_pool_recycle(plan.bluesteinpool, ref bufd);
+                    rowidx = rowidx+1;
+                    continue;
+                }
+                
+                //
+                // Process Rader's FFT
+                //
+                if( operation==opradersfft )
+                {
+                    ftradersfft(plan, a, abase, aoffset, operandscnt, operandsize, rowidx+plan.entries[rowidx,colparam0], plan.entries[rowidx,colparam1], plan.entries[rowidx,colparam2], plan.entries[rowidx,colparam3], buf, _params);
+                    rowidx = rowidx+1;
+                    continue;
+                }
+                
+                //
+                // Process "complex twiddle factors" operation
+                //
+                if( operation==opcomplexfftfactors )
+                {
+                    alglib.ap.assert(microvectorsize==2, "FTApplySubPlan: MicrovectorSize<>1");
+                    n1 = plan.entries[rowidx,colparam0];
+                    n2 = operandsize/n1;
+                    for(i=0; i<=operandscnt-1; i++)
+                    {
+                        ffttwcalc(a, abase+aoffset+i*operandsize*2, n1, n2, _params);
+                    }
+                    rowidx = rowidx+1;
+                    continue;
+                }
+                
+                //
+                // Process "complex transposition" operation
+                //
+                if( operation==opcomplextranspose )
+                {
+                    alglib.ap.assert(microvectorsize==2, "FTApplySubPlan: MicrovectorSize<>1");
+                    n1 = plan.entries[rowidx,colparam0];
+                    n2 = operandsize/n1;
+                    for(i=0; i<=operandscnt-1; i++)
+                    {
+                        internalcomplexlintranspose(a, n1, n2, abase+aoffset+i*operandsize*2, buf, _params);
+                    }
+                    rowidx = rowidx+1;
+                    continue;
+                }
+                
+                //
+                // Error
+                //
+                alglib.ap.assert(false, "FTApplySubPlan: unexpected plan type");
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine applies complex reference FFT to input/output array A.
+
+        VERY SLOW OPERATION, do not use it in real life plans :)
+
+        INPUT PARAMETERS:
+            A           -   array, must be large enough for plan to work
+            Offs        -   offset of the subarray to process
+            OperandsCnt -   operands count (see description of FastTransformPlan)
+            OperandSize -   operand size (see description of FastTransformPlan)
+            MicrovectorSize-microvector size (see description of FastTransformPlan)
+            Buf         -   temporary array, must be at least OperandsCnt*OperandSize*MicrovectorSize
+            
+        OUTPUT PARAMETERS:
+            A           -   transformed array
+
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftapplycomplexreffft(double[] a,
+            int offs,
+            int operandscnt,
+            int operandsize,
+            int microvectorsize,
+            double[] buf,
+            alglib.xparams _params)
+        {
+            int opidx = 0;
+            int i = 0;
+            int k = 0;
+            double hre = 0;
+            double him = 0;
+            double c = 0;
+            double s = 0;
+            double re = 0;
+            double im = 0;
+            int n = 0;
+
+            alglib.ap.assert(operandscnt>=1, "FTApplyComplexRefFFT: OperandsCnt<1");
+            alglib.ap.assert(operandsize>=1, "FTApplyComplexRefFFT: OperandSize<1");
+            alglib.ap.assert(microvectorsize==2, "FTApplyComplexRefFFT: MicrovectorSize<>2");
+            n = operandsize;
+            for(opidx=0; opidx<=operandscnt-1; opidx++)
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    hre = 0;
+                    him = 0;
+                    for(k=0; k<=n-1; k++)
+                    {
+                        re = a[offs+opidx*operandsize*2+2*k+0];
+                        im = a[offs+opidx*operandsize*2+2*k+1];
+                        c = Math.Cos(-(2*Math.PI*k*i/n));
+                        s = Math.Sin(-(2*Math.PI*k*i/n));
+                        hre = hre+c*re-s*im;
+                        him = him+c*im+s*re;
+                    }
+                    buf[2*i+0] = hre;
+                    buf[2*i+1] = him;
+                }
+                for(i=0; i<=operandsize*2-1; i++)
+                {
+                    a[offs+opidx*operandsize*2+i] = buf[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine applies complex codelet FFT to input/output array A.
+
+        INPUT PARAMETERS:
+            A           -   array, must be large enough for plan to work
+            Offs        -   offset of the subarray to process
+            OperandsCnt -   operands count (see description of FastTransformPlan)
+            OperandSize -   operand size (see description of FastTransformPlan)
+            MicrovectorSize-microvector size, must be 2
+            
+        OUTPUT PARAMETERS:
+            A           -   transformed array
+
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftapplycomplexcodeletfft(double[] a,
+            int offs,
+            int operandscnt,
+            int operandsize,
+            int microvectorsize,
+            alglib.xparams _params)
+        {
+            int opidx = 0;
+            int n = 0;
+            int aoffset = 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 a4x = 0;
+            double a4y = 0;
+            double a5x = 0;
+            double a5y = 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 v = 0;
+
+            alglib.ap.assert(operandscnt>=1, "FTApplyComplexCodeletFFT: OperandsCnt<1");
+            alglib.ap.assert(operandsize>=1, "FTApplyComplexCodeletFFT: OperandSize<1");
+            alglib.ap.assert(microvectorsize==2, "FTApplyComplexCodeletFFT: MicrovectorSize<>2");
+            n = operandsize;
+            
+            //
+            // Hard-coded transforms for different N's
+            //
+            alglib.ap.assert(n<=maxradix, "FTApplyComplexCodeletFFT: N>MaxRadix");
+            if( n==2 )
+            {
+                for(opidx=0; opidx<=operandscnt-1; opidx++)
+                {
+                    aoffset = offs+opidx*operandsize*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 )
+            {
+                c1 = Math.Cos(2*Math.PI/3)-1;
+                c2 = Math.Sin(2*Math.PI/3);
+                for(opidx=0; opidx<=operandscnt-1; opidx++)
+                {
+                    aoffset = offs+opidx*operandsize*2;
+                    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 )
+            {
+                for(opidx=0; opidx<=operandscnt-1; opidx++)
+                {
+                    aoffset = offs+opidx*operandsize*2;
+                    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 )
+            {
+                v = 2*Math.PI/5;
+                c1 = (Math.Cos(v)+Math.Cos(2*v))/2-1;
+                c2 = (Math.Cos(v)-Math.Cos(2*v))/2;
+                c3 = -Math.Sin(v);
+                c4 = -(Math.Sin(v)+Math.Sin(2*v));
+                c5 = Math.Sin(v)-Math.Sin(2*v);
+                for(opidx=0; opidx<=operandscnt-1; opidx++)
+                {
+                    aoffset = offs+opidx*operandsize*2;
+                    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( n==6 )
+            {
+                c1 = Math.Cos(2*Math.PI/3)-1;
+                c2 = Math.Sin(2*Math.PI/3);
+                c3 = Math.Cos(-(Math.PI/3));
+                c4 = Math.Sin(-(Math.PI/3));
+                for(opidx=0; opidx<=operandscnt-1; opidx++)
+                {
+                    aoffset = offs+opidx*operandsize*2;
+                    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];
+                    a4x = a[aoffset+8];
+                    a4y = a[aoffset+9];
+                    a5x = a[aoffset+10];
+                    a5y = a[aoffset+11];
+                    v0 = a0x;
+                    v1 = a0y;
+                    a0x = a0x+a3x;
+                    a0y = a0y+a3y;
+                    a3x = v0-a3x;
+                    a3y = v1-a3y;
+                    v0 = a1x;
+                    v1 = a1y;
+                    a1x = a1x+a4x;
+                    a1y = a1y+a4y;
+                    a4x = v0-a4x;
+                    a4y = v1-a4y;
+                    v0 = a2x;
+                    v1 = a2y;
+                    a2x = a2x+a5x;
+                    a2y = a2y+a5y;
+                    a5x = v0-a5x;
+                    a5y = v1-a5y;
+                    t4x = a4x*c3-a4y*c4;
+                    t4y = a4x*c4+a4y*c3;
+                    a4x = t4x;
+                    a4y = t4y;
+                    t5x = -(a5x*c3)-a5y*c4;
+                    t5y = a5x*c4-a5y*c3;
+                    a5x = t5x;
+                    a5y = t5y;
+                    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;
+                    t1x = a4x+a5x;
+                    t1y = a4y+a5y;
+                    a3x = a3x+t1x;
+                    a3y = a3y+t1y;
+                    m1x = c1*t1x;
+                    m1y = c1*t1y;
+                    m2x = c2*(a4y-a5y);
+                    m2y = c2*(a5x-a4x);
+                    s1x = a3x+m1x;
+                    s1y = a3y+m1y;
+                    a4x = s1x+m2x;
+                    a4y = s1y+m2y;
+                    a5x = s1x-m2x;
+                    a5y = s1y-m2y;
+                    a[aoffset+0] = a0x;
+                    a[aoffset+1] = a0y;
+                    a[aoffset+2] = a3x;
+                    a[aoffset+3] = a3y;
+                    a[aoffset+4] = a1x;
+                    a[aoffset+5] = a1y;
+                    a[aoffset+6] = a4x;
+                    a[aoffset+7] = a4y;
+                    a[aoffset+8] = a2x;
+                    a[aoffset+9] = a2y;
+                    a[aoffset+10] = a5x;
+                    a[aoffset+11] = a5y;
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine applies complex "integrated" codelet FFT  to  input/output
+        array A. "Integrated" codelet differs from "normal" one in following ways:
+        * it can work with MicrovectorSize>1
+        * hence, it can be used in Cooley-Tukey FFT without transpositions
+        * it performs inlined multiplication by twiddle factors of Cooley-Tukey
+          FFT with N2=MicrovectorSize/2.
+
+        INPUT PARAMETERS:
+            A           -   array, must be large enough for plan to work
+            Offs        -   offset of the subarray to process
+            OperandsCnt -   operands count (see description of FastTransformPlan)
+            OperandSize -   operand size (see description of FastTransformPlan)
+            MicrovectorSize-microvector size, must be 1
+            
+        OUTPUT PARAMETERS:
+            A           -   transformed array
+
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftapplycomplexcodelettwfft(double[] a,
+            int offs,
+            int operandscnt,
+            int operandsize,
+            int microvectorsize,
+            alglib.xparams _params)
+        {
+            int opidx = 0;
+            int mvidx = 0;
+            int n = 0;
+            int m = 0;
+            int aoffset0 = 0;
+            int aoffset2 = 0;
+            int aoffset4 = 0;
+            int aoffset6 = 0;
+            int aoffset8 = 0;
+            int aoffset10 = 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 a4x = 0;
+            double a4y = 0;
+            double a5x = 0;
+            double a5y = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double v3 = 0;
+            double q0x = 0;
+            double q0y = 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 v = 0;
+            double tw0 = 0;
+            double tw1 = 0;
+            double twx = 0;
+            double twxm1 = 0;
+            double twy = 0;
+            double tw2x = 0;
+            double tw2y = 0;
+            double tw3x = 0;
+            double tw3y = 0;
+            double tw4x = 0;
+            double tw4y = 0;
+            double tw5x = 0;
+            double tw5y = 0;
+
+            alglib.ap.assert(operandscnt>=1, "FTApplyComplexCodeletFFT: OperandsCnt<1");
+            alglib.ap.assert(operandsize>=1, "FTApplyComplexCodeletFFT: OperandSize<1");
+            alglib.ap.assert(microvectorsize>=1, "FTApplyComplexCodeletFFT: MicrovectorSize<>1");
+            alglib.ap.assert(microvectorsize%2==0, "FTApplyComplexCodeletFFT: MicrovectorSize is not even");
+            n = operandsize;
+            m = microvectorsize/2;
+            
+            //
+            // Hard-coded transforms for different N's
+            //
+            alglib.ap.assert(n<=maxradix, "FTApplyComplexCodeletTwFFT: N>MaxRadix");
+            if( n==2 )
+            {
+                v = -(2*Math.PI/(n*m));
+                tw0 = -(2*math.sqr(Math.Sin(0.5*v)));
+                tw1 = Math.Sin(v);
+                for(opidx=0; opidx<=operandscnt-1; opidx++)
+                {
+                    aoffset0 = offs+opidx*operandsize*microvectorsize;
+                    aoffset2 = aoffset0+microvectorsize;
+                    twxm1 = 0.0;
+                    twy = 0.0;
+                    for(mvidx=0; mvidx<=m-1; mvidx++)
+                    {
+                        a0x = a[aoffset0];
+                        a0y = a[aoffset0+1];
+                        a1x = a[aoffset2];
+                        a1y = a[aoffset2+1];
+                        v0 = a0x+a1x;
+                        v1 = a0y+a1y;
+                        v2 = a0x-a1x;
+                        v3 = a0y-a1y;
+                        a[aoffset0] = v0;
+                        a[aoffset0+1] = v1;
+                        a[aoffset2] = v2*(1+twxm1)-v3*twy;
+                        a[aoffset2+1] = v3*(1+twxm1)+v2*twy;
+                        aoffset0 = aoffset0+2;
+                        aoffset2 = aoffset2+2;
+                        if( (mvidx+1)%updatetw==0 )
+                        {
+                            v = -(2*Math.PI*(mvidx+1)/(n*m));
+                            twxm1 = Math.Sin(0.5*v);
+                            twxm1 = -(2*twxm1*twxm1);
+                            twy = Math.Sin(v);
+                        }
+                        else
+                        {
+                            v = twxm1+tw0+twxm1*tw0-twy*tw1;
+                            twy = twy+tw1+twxm1*tw1+twy*tw0;
+                            twxm1 = v;
+                        }
+                    }
+                }
+                return;
+            }
+            if( n==3 )
+            {
+                v = -(2*Math.PI/(n*m));
+                tw0 = -(2*math.sqr(Math.Sin(0.5*v)));
+                tw1 = Math.Sin(v);
+                c1 = Math.Cos(2*Math.PI/3)-1;
+                c2 = Math.Sin(2*Math.PI/3);
+                for(opidx=0; opidx<=operandscnt-1; opidx++)
+                {
+                    aoffset0 = offs+opidx*operandsize*microvectorsize;
+                    aoffset2 = aoffset0+microvectorsize;
+                    aoffset4 = aoffset2+microvectorsize;
+                    twx = 1.0;
+                    twxm1 = 0.0;
+                    twy = 0.0;
+                    for(mvidx=0; mvidx<=m-1; mvidx++)
+                    {
+                        a0x = a[aoffset0];
+                        a0y = a[aoffset0+1];
+                        a1x = a[aoffset2];
+                        a1y = a[aoffset2+1];
+                        a2x = a[aoffset4];
+                        a2y = a[aoffset4+1];
+                        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;
+                        tw2x = twx*twx-twy*twy;
+                        tw2y = 2*twx*twy;
+                        a[aoffset0] = a0x;
+                        a[aoffset0+1] = a0y;
+                        a[aoffset2] = a1x*twx-a1y*twy;
+                        a[aoffset2+1] = a1y*twx+a1x*twy;
+                        a[aoffset4] = a2x*tw2x-a2y*tw2y;
+                        a[aoffset4+1] = a2y*tw2x+a2x*tw2y;
+                        aoffset0 = aoffset0+2;
+                        aoffset2 = aoffset2+2;
+                        aoffset4 = aoffset4+2;
+                        if( (mvidx+1)%updatetw==0 )
+                        {
+                            v = -(2*Math.PI*(mvidx+1)/(n*m));
+                            twxm1 = Math.Sin(0.5*v);
+                            twxm1 = -(2*twxm1*twxm1);
+                            twy = Math.Sin(v);
+                            twx = twxm1+1;
+                        }
+                        else
+                        {
+                            v = twxm1+tw0+twxm1*tw0-twy*tw1;
+                            twy = twy+tw1+twxm1*tw1+twy*tw0;
+                            twxm1 = v;
+                            twx = v+1;
+                        }
+                    }
+                }
+                return;
+            }
+            if( n==4 )
+            {
+                v = -(2*Math.PI/(n*m));
+                tw0 = -(2*math.sqr(Math.Sin(0.5*v)));
+                tw1 = Math.Sin(v);
+                for(opidx=0; opidx<=operandscnt-1; opidx++)
+                {
+                    aoffset0 = offs+opidx*operandsize*microvectorsize;
+                    aoffset2 = aoffset0+microvectorsize;
+                    aoffset4 = aoffset2+microvectorsize;
+                    aoffset6 = aoffset4+microvectorsize;
+                    twx = 1.0;
+                    twxm1 = 0.0;
+                    twy = 0.0;
+                    for(mvidx=0; mvidx<=m-1; mvidx++)
+                    {
+                        a0x = a[aoffset0];
+                        a0y = a[aoffset0+1];
+                        a1x = a[aoffset2];
+                        a1y = a[aoffset2+1];
+                        a2x = a[aoffset4];
+                        a2y = a[aoffset4+1];
+                        a3x = a[aoffset6];
+                        a3y = a[aoffset6+1];
+                        t1x = a0x+a2x;
+                        t1y = a0y+a2y;
+                        t2x = a1x+a3x;
+                        t2y = a1y+a3y;
+                        m2x = a0x-a2x;
+                        m2y = a0y-a2y;
+                        m3x = a1y-a3y;
+                        m3y = a3x-a1x;
+                        tw2x = twx*twx-twy*twy;
+                        tw2y = 2*twx*twy;
+                        tw3x = twx*tw2x-twy*tw2y;
+                        tw3y = twx*tw2y+twy*tw2x;
+                        a1x = m2x+m3x;
+                        a1y = m2y+m3y;
+                        a2x = t1x-t2x;
+                        a2y = t1y-t2y;
+                        a3x = m2x-m3x;
+                        a3y = m2y-m3y;
+                        a[aoffset0] = t1x+t2x;
+                        a[aoffset0+1] = t1y+t2y;
+                        a[aoffset2] = a1x*twx-a1y*twy;
+                        a[aoffset2+1] = a1y*twx+a1x*twy;
+                        a[aoffset4] = a2x*tw2x-a2y*tw2y;
+                        a[aoffset4+1] = a2y*tw2x+a2x*tw2y;
+                        a[aoffset6] = a3x*tw3x-a3y*tw3y;
+                        a[aoffset6+1] = a3y*tw3x+a3x*tw3y;
+                        aoffset0 = aoffset0+2;
+                        aoffset2 = aoffset2+2;
+                        aoffset4 = aoffset4+2;
+                        aoffset6 = aoffset6+2;
+                        if( (mvidx+1)%updatetw==0 )
+                        {
+                            v = -(2*Math.PI*(mvidx+1)/(n*m));
+                            twxm1 = Math.Sin(0.5*v);
+                            twxm1 = -(2*twxm1*twxm1);
+                            twy = Math.Sin(v);
+                            twx = twxm1+1;
+                        }
+                        else
+                        {
+                            v = twxm1+tw0+twxm1*tw0-twy*tw1;
+                            twy = twy+tw1+twxm1*tw1+twy*tw0;
+                            twxm1 = v;
+                            twx = v+1;
+                        }
+                    }
+                }
+                return;
+            }
+            if( n==5 )
+            {
+                v = -(2*Math.PI/(n*m));
+                tw0 = -(2*math.sqr(Math.Sin(0.5*v)));
+                tw1 = Math.Sin(v);
+                v = 2*Math.PI/5;
+                c1 = (Math.Cos(v)+Math.Cos(2*v))/2-1;
+                c2 = (Math.Cos(v)-Math.Cos(2*v))/2;
+                c3 = -Math.Sin(v);
+                c4 = -(Math.Sin(v)+Math.Sin(2*v));
+                c5 = Math.Sin(v)-Math.Sin(2*v);
+                for(opidx=0; opidx<=operandscnt-1; opidx++)
+                {
+                    aoffset0 = offs+opidx*operandsize*microvectorsize;
+                    aoffset2 = aoffset0+microvectorsize;
+                    aoffset4 = aoffset2+microvectorsize;
+                    aoffset6 = aoffset4+microvectorsize;
+                    aoffset8 = aoffset6+microvectorsize;
+                    twx = 1.0;
+                    twxm1 = 0.0;
+                    twy = 0.0;
+                    for(mvidx=0; mvidx<=m-1; mvidx++)
+                    {
+                        a0x = a[aoffset0];
+                        a0y = a[aoffset0+1];
+                        a1x = a[aoffset2];
+                        a1y = a[aoffset2+1];
+                        a2x = a[aoffset4];
+                        a2y = a[aoffset4+1];
+                        a3x = a[aoffset6];
+                        a3y = a[aoffset6+1];
+                        a4x = a[aoffset8];
+                        a4y = a[aoffset8+1];
+                        t1x = a1x+a4x;
+                        t1y = a1y+a4y;
+                        t2x = a2x+a3x;
+                        t2y = a2y+a3y;
+                        t3x = a1x-a4x;
+                        t3y = a1y-a4y;
+                        t4x = a3x-a2x;
+                        t4y = a3y-a2y;
+                        t5x = t1x+t2x;
+                        t5y = t1y+t2y;
+                        q0x = a0x+t5x;
+                        q0y = a0y+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 = q0x+m1x;
+                        s1y = q0y+m1y;
+                        s2x = s1x+m2x;
+                        s2y = s1y+m2y;
+                        s4x = s1x-m2x;
+                        s4y = s1y-m2y;
+                        tw2x = twx*twx-twy*twy;
+                        tw2y = 2*twx*twy;
+                        tw3x = twx*tw2x-twy*tw2y;
+                        tw3y = twx*tw2y+twy*tw2x;
+                        tw4x = tw2x*tw2x-tw2y*tw2y;
+                        tw4y = tw2x*tw2y+tw2y*tw2x;
+                        a1x = s2x+s3x;
+                        a1y = s2y+s3y;
+                        a2x = s4x+s5x;
+                        a2y = s4y+s5y;
+                        a3x = s4x-s5x;
+                        a3y = s4y-s5y;
+                        a4x = s2x-s3x;
+                        a4y = s2y-s3y;
+                        a[aoffset0] = q0x;
+                        a[aoffset0+1] = q0y;
+                        a[aoffset2] = a1x*twx-a1y*twy;
+                        a[aoffset2+1] = a1x*twy+a1y*twx;
+                        a[aoffset4] = a2x*tw2x-a2y*tw2y;
+                        a[aoffset4+1] = a2x*tw2y+a2y*tw2x;
+                        a[aoffset6] = a3x*tw3x-a3y*tw3y;
+                        a[aoffset6+1] = a3x*tw3y+a3y*tw3x;
+                        a[aoffset8] = a4x*tw4x-a4y*tw4y;
+                        a[aoffset8+1] = a4x*tw4y+a4y*tw4x;
+                        aoffset0 = aoffset0+2;
+                        aoffset2 = aoffset2+2;
+                        aoffset4 = aoffset4+2;
+                        aoffset6 = aoffset6+2;
+                        aoffset8 = aoffset8+2;
+                        if( (mvidx+1)%updatetw==0 )
+                        {
+                            v = -(2*Math.PI*(mvidx+1)/(n*m));
+                            twxm1 = Math.Sin(0.5*v);
+                            twxm1 = -(2*twxm1*twxm1);
+                            twy = Math.Sin(v);
+                            twx = twxm1+1;
+                        }
+                        else
+                        {
+                            v = twxm1+tw0+twxm1*tw0-twy*tw1;
+                            twy = twy+tw1+twxm1*tw1+twy*tw0;
+                            twxm1 = v;
+                            twx = v+1;
+                        }
+                    }
+                }
+                return;
+            }
+            if( n==6 )
+            {
+                c1 = Math.Cos(2*Math.PI/3)-1;
+                c2 = Math.Sin(2*Math.PI/3);
+                c3 = Math.Cos(-(Math.PI/3));
+                c4 = Math.Sin(-(Math.PI/3));
+                v = -(2*Math.PI/(n*m));
+                tw0 = -(2*math.sqr(Math.Sin(0.5*v)));
+                tw1 = Math.Sin(v);
+                for(opidx=0; opidx<=operandscnt-1; opidx++)
+                {
+                    aoffset0 = offs+opidx*operandsize*microvectorsize;
+                    aoffset2 = aoffset0+microvectorsize;
+                    aoffset4 = aoffset2+microvectorsize;
+                    aoffset6 = aoffset4+microvectorsize;
+                    aoffset8 = aoffset6+microvectorsize;
+                    aoffset10 = aoffset8+microvectorsize;
+                    twx = 1.0;
+                    twxm1 = 0.0;
+                    twy = 0.0;
+                    for(mvidx=0; mvidx<=m-1; mvidx++)
+                    {
+                        a0x = a[aoffset0+0];
+                        a0y = a[aoffset0+1];
+                        a1x = a[aoffset2+0];
+                        a1y = a[aoffset2+1];
+                        a2x = a[aoffset4+0];
+                        a2y = a[aoffset4+1];
+                        a3x = a[aoffset6+0];
+                        a3y = a[aoffset6+1];
+                        a4x = a[aoffset8+0];
+                        a4y = a[aoffset8+1];
+                        a5x = a[aoffset10+0];
+                        a5y = a[aoffset10+1];
+                        v0 = a0x;
+                        v1 = a0y;
+                        a0x = a0x+a3x;
+                        a0y = a0y+a3y;
+                        a3x = v0-a3x;
+                        a3y = v1-a3y;
+                        v0 = a1x;
+                        v1 = a1y;
+                        a1x = a1x+a4x;
+                        a1y = a1y+a4y;
+                        a4x = v0-a4x;
+                        a4y = v1-a4y;
+                        v0 = a2x;
+                        v1 = a2y;
+                        a2x = a2x+a5x;
+                        a2y = a2y+a5y;
+                        a5x = v0-a5x;
+                        a5y = v1-a5y;
+                        t4x = a4x*c3-a4y*c4;
+                        t4y = a4x*c4+a4y*c3;
+                        a4x = t4x;
+                        a4y = t4y;
+                        t5x = -(a5x*c3)-a5y*c4;
+                        t5y = a5x*c4-a5y*c3;
+                        a5x = t5x;
+                        a5y = t5y;
+                        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;
+                        t1x = a4x+a5x;
+                        t1y = a4y+a5y;
+                        a3x = a3x+t1x;
+                        a3y = a3y+t1y;
+                        m1x = c1*t1x;
+                        m1y = c1*t1y;
+                        m2x = c2*(a4y-a5y);
+                        m2y = c2*(a5x-a4x);
+                        s1x = a3x+m1x;
+                        s1y = a3y+m1y;
+                        a4x = s1x+m2x;
+                        a4y = s1y+m2y;
+                        a5x = s1x-m2x;
+                        a5y = s1y-m2y;
+                        tw2x = twx*twx-twy*twy;
+                        tw2y = 2*twx*twy;
+                        tw3x = twx*tw2x-twy*tw2y;
+                        tw3y = twx*tw2y+twy*tw2x;
+                        tw4x = tw2x*tw2x-tw2y*tw2y;
+                        tw4y = 2*tw2x*tw2y;
+                        tw5x = tw3x*tw2x-tw3y*tw2y;
+                        tw5y = tw3x*tw2y+tw3y*tw2x;
+                        a[aoffset0+0] = a0x;
+                        a[aoffset0+1] = a0y;
+                        a[aoffset2+0] = a3x*twx-a3y*twy;
+                        a[aoffset2+1] = a3y*twx+a3x*twy;
+                        a[aoffset4+0] = a1x*tw2x-a1y*tw2y;
+                        a[aoffset4+1] = a1y*tw2x+a1x*tw2y;
+                        a[aoffset6+0] = a4x*tw3x-a4y*tw3y;
+                        a[aoffset6+1] = a4y*tw3x+a4x*tw3y;
+                        a[aoffset8+0] = a2x*tw4x-a2y*tw4y;
+                        a[aoffset8+1] = a2y*tw4x+a2x*tw4y;
+                        a[aoffset10+0] = a5x*tw5x-a5y*tw5y;
+                        a[aoffset10+1] = a5y*tw5x+a5x*tw5y;
+                        aoffset0 = aoffset0+2;
+                        aoffset2 = aoffset2+2;
+                        aoffset4 = aoffset4+2;
+                        aoffset6 = aoffset6+2;
+                        aoffset8 = aoffset8+2;
+                        aoffset10 = aoffset10+2;
+                        if( (mvidx+1)%updatetw==0 )
+                        {
+                            v = -(2*Math.PI*(mvidx+1)/(n*m));
+                            twxm1 = Math.Sin(0.5*v);
+                            twxm1 = -(2*twxm1*twxm1);
+                            twy = Math.Sin(v);
+                            twx = twxm1+1;
+                        }
+                        else
+                        {
+                            v = twxm1+tw0+twxm1*tw0-twy*tw1;
+                            twy = twy+tw1+twxm1*tw1+twy*tw0;
+                            twxm1 = v;
+                            twx = v+1;
+                        }
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine precomputes data for complex Bluestein's  FFT  and  writes
+        them to array PrecR[] at specified offset. It  is  responsibility  of  the
+        caller to make sure that PrecR[] is large enough.
+
+        INPUT PARAMETERS:
+            N           -   original size of the transform
+            M           -   size of the "padded" Bluestein's transform
+            PrecR       -   preallocated array
+            Offs        -   offset
+            
+        OUTPUT PARAMETERS:
+            PrecR       -   data at Offs:Offs+4*M-1 are modified:
+                            * PrecR[Offs:Offs+2*M-1] stores Z[k]=exp(i*pi*k^2/N)
+                            * PrecR[Offs+2*M:Offs+4*M-1] stores FFT of the Z
+                            Other parts of PrecR are unchanged.
+                            
+        NOTE: this function performs internal M-point FFT. It allocates temporary
+              plan which is destroyed after leaving this function.
+
+          -- ALGLIB --
+             Copyright 08.05.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftprecomputebluesteinsfft(int n,
+            int m,
+            double[] precr,
+            int offs,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double bx = 0;
+            double by = 0;
+            fasttransformplan plan = new fasttransformplan();
+
+            
+            //
+            // Fill first half of PrecR with b[k] = exp(i*pi*k^2/N)
+            //
+            for(i=0; i<=2*m-1; i++)
+            {
+                precr[offs+i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                bx = Math.Cos(Math.PI/n*i*i);
+                by = Math.Sin(Math.PI/n*i*i);
+                precr[offs+2*i+0] = bx;
+                precr[offs+2*i+1] = by;
+                precr[offs+2*((m-i)%m)+0] = bx;
+                precr[offs+2*((m-i)%m)+1] = by;
+            }
+            
+            //
+            // Precomputed FFT
+            //
+            ftcomplexfftplan(m, 1, plan, _params);
+            for(i=0; i<=2*m-1; i++)
+            {
+                precr[offs+2*m+i] = precr[offs+i];
+            }
+            ftapplysubplan(plan, 0, precr, offs+2*m, 0, plan.buffer, 1, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine applies complex Bluestein's FFT to input/output array A.
+
+        INPUT PARAMETERS:
+            Plan        -   transformation plan
+            A           -   array, must be large enough for plan to work
+            ABase       -   base offset in array A, this value points to start of
+                            subarray whose length is equal to length of the plan
+            AOffset     -   offset with respect to ABase, 0<=AOffset<PlanLength.
+                            This is an offset within large PlanLength-subarray of
+                            the chunk to process.
+            OperandsCnt -   number of repeated operands (length N each)
+            N           -   original data length (measured in complex numbers)
+            M           -   padded data length (measured in complex numbers)
+            PrecOffs    -   offset of the precomputed data for the plan
+            SubPlan     -   position of the length-M FFT subplan which is used by
+                            transformation
+            BufA        -   temporary buffer, at least 2*M elements
+            BufB        -   temporary buffer, at least 2*M elements
+            BufC        -   temporary buffer, at least 2*M elements
+            BufD        -   temporary buffer, at least 2*M elements
+            
+        OUTPUT PARAMETERS:
+            A           -   transformed array
+
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftbluesteinsfft(fasttransformplan plan,
+            double[] a,
+            int abase,
+            int aoffset,
+            int operandscnt,
+            int n,
+            int m,
+            int precoffs,
+            int subplan,
+            double[] bufa,
+            double[] bufb,
+            double[] bufc,
+            double[] bufd,
+            alglib.xparams _params)
+        {
+            int op = 0;
+            int i = 0;
+            double x = 0;
+            double y = 0;
+            double bx = 0;
+            double by = 0;
+            double ax = 0;
+            double ay = 0;
+            double rx = 0;
+            double ry = 0;
+            int p0 = 0;
+            int p1 = 0;
+            int p2 = 0;
+
+            for(op=0; op<=operandscnt-1; op++)
+            {
+                
+                //
+                // Multiply A by conj(Z), store to buffer.
+                // Pad A by zeros.
+                //
+                // NOTE: Z[k]=exp(i*pi*k^2/N)
+                //
+                p0 = abase+aoffset+op*2*n;
+                p1 = precoffs;
+                for(i=0; i<=n-1; i++)
+                {
+                    x = a[p0+0];
+                    y = a[p0+1];
+                    bx = plan.precr[p1+0];
+                    by = -plan.precr[p1+1];
+                    bufa[2*i+0] = x*bx-y*by;
+                    bufa[2*i+1] = x*by+y*bx;
+                    p0 = p0+2;
+                    p1 = p1+2;
+                }
+                for(i=2*n; i<=2*m-1; i++)
+                {
+                    bufa[i] = 0;
+                }
+                
+                //
+                // Perform convolution of A and Z (using precomputed
+                // FFT of Z stored in Plan structure).
+                //
+                ftapplysubplan(plan, subplan, bufa, 0, 0, bufc, 1, _params);
+                p0 = 0;
+                p1 = precoffs+2*m;
+                for(i=0; i<=m-1; i++)
+                {
+                    ax = bufa[p0+0];
+                    ay = bufa[p0+1];
+                    bx = plan.precr[p1+0];
+                    by = plan.precr[p1+1];
+                    bufa[p0+0] = ax*bx-ay*by;
+                    bufa[p0+1] = -(ax*by+ay*bx);
+                    p0 = p0+2;
+                    p1 = p1+2;
+                }
+                ftapplysubplan(plan, subplan, bufa, 0, 0, bufc, 1, _params);
+                
+                //
+                // Post processing:
+                //     A:=conj(Z)*conj(A)/M
+                // Here conj(A)/M corresponds to last stage of inverse DFT,
+                // and conj(Z) comes from Bluestein's FFT algorithm.
+                //
+                p0 = precoffs;
+                p1 = 0;
+                p2 = abase+aoffset+op*2*n;
+                for(i=0; i<=n-1; i++)
+                {
+                    bx = plan.precr[p0+0];
+                    by = plan.precr[p0+1];
+                    rx = bufa[p1+0]/m;
+                    ry = -(bufa[p1+1]/m);
+                    a[p2+0] = rx*bx-ry*-by;
+                    a[p2+1] = rx*-by+ry*bx;
+                    p0 = p0+2;
+                    p1 = p1+2;
+                    p2 = p2+2;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine precomputes data for complex Rader's FFT and  writes  them
+        to array PrecR[] at specified offset. It  is  responsibility of the caller
+        to make sure that PrecR[] is large enough.
+
+        INPUT PARAMETERS:
+            N           -   original size of the transform (before reduction to N-1)
+            RQ          -   primitive root modulo N
+            RIQ         -   inverse of primitive root modulo N
+            PrecR       -   preallocated array
+            Offs        -   offset
+            
+        OUTPUT PARAMETERS:
+            PrecR       -   data at Offs:Offs+2*(N-1)-1 store FFT of Rader's factors,
+                            other parts of PrecR are unchanged.
+                            
+        NOTE: this function performs internal (N-1)-point FFT. It allocates temporary
+              plan which is destroyed after leaving this function.
+
+          -- ALGLIB --
+             Copyright 08.05.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftprecomputeradersfft(int n,
+            int rq,
+            int riq,
+            double[] precr,
+            int offs,
+            alglib.xparams _params)
+        {
+            int q = 0;
+            fasttransformplan plan = new fasttransformplan();
+            int kiq = 0;
+            double v = 0;
+
+            
+            //
+            // Fill PrecR with Rader factors, perform FFT
+            //
+            kiq = 1;
+            for(q=0; q<=n-2; q++)
+            {
+                v = -(2*Math.PI*kiq/n);
+                precr[offs+2*q+0] = Math.Cos(v);
+                precr[offs+2*q+1] = Math.Sin(v);
+                kiq = kiq*riq%n;
+            }
+            ftcomplexfftplan(n-1, 1, plan, _params);
+            ftapplysubplan(plan, 0, precr, offs, 0, plan.buffer, 1, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine applies complex Rader's FFT to input/output array A.
+
+        INPUT PARAMETERS:
+            A           -   array, must be large enough for plan to work
+            ABase       -   base offset in array A, this value points to start of
+                            subarray whose length is equal to length of the plan
+            AOffset     -   offset with respect to ABase, 0<=AOffset<PlanLength.
+                            This is an offset within large PlanLength-subarray of
+                            the chunk to process.
+            OperandsCnt -   number of repeated operands (length N each)
+            N           -   original data length (measured in complex numbers)
+            SubPlan     -   position of the (N-1)-point FFT subplan which is used
+                            by transformation
+            RQ          -   primitive root modulo N
+            RIQ         -   inverse of primitive root modulo N
+            PrecOffs    -   offset of the precomputed data for the plan
+            Buf         -   temporary array
+            
+        OUTPUT PARAMETERS:
+            A           -   transformed array
+
+          -- ALGLIB --
+             Copyright 05.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftradersfft(fasttransformplan plan,
+            double[] a,
+            int abase,
+            int aoffset,
+            int operandscnt,
+            int n,
+            int subplan,
+            int rq,
+            int riq,
+            int precoffs,
+            double[] buf,
+            alglib.xparams _params)
+        {
+            int opidx = 0;
+            int i = 0;
+            int q = 0;
+            int kq = 0;
+            int kiq = 0;
+            double x0 = 0;
+            double y0 = 0;
+            int p0 = 0;
+            int p1 = 0;
+            double ax = 0;
+            double ay = 0;
+            double bx = 0;
+            double by = 0;
+            double rx = 0;
+            double ry = 0;
+
+            alglib.ap.assert(operandscnt>=1, "FTApplyComplexRefFFT: OperandsCnt<1");
+            
+            //
+            // Process operands
+            //
+            for(opidx=0; opidx<=operandscnt-1; opidx++)
+            {
+                
+                //
+                // fill QA
+                //
+                kq = 1;
+                p0 = abase+aoffset+opidx*n*2;
+                p1 = aoffset+opidx*n*2;
+                rx = a[p0+0];
+                ry = a[p0+1];
+                x0 = rx;
+                y0 = ry;
+                for(q=0; q<=n-2; q++)
+                {
+                    ax = a[p0+2*kq+0];
+                    ay = a[p0+2*kq+1];
+                    buf[p1+0] = ax;
+                    buf[p1+1] = ay;
+                    rx = rx+ax;
+                    ry = ry+ay;
+                    kq = kq*rq%n;
+                    p1 = p1+2;
+                }
+                p0 = abase+aoffset+opidx*n*2;
+                p1 = aoffset+opidx*n*2;
+                for(q=0; q<=n-2; q++)
+                {
+                    a[p0] = buf[p1];
+                    a[p0+1] = buf[p1+1];
+                    p0 = p0+2;
+                    p1 = p1+2;
+                }
+                
+                //
+                // Convolution
+                //
+                ftapplysubplan(plan, subplan, a, abase, aoffset+opidx*n*2, buf, 1, _params);
+                p0 = abase+aoffset+opidx*n*2;
+                p1 = precoffs;
+                for(i=0; i<=n-2; i++)
+                {
+                    ax = a[p0+0];
+                    ay = a[p0+1];
+                    bx = plan.precr[p1+0];
+                    by = plan.precr[p1+1];
+                    a[p0+0] = ax*bx-ay*by;
+                    a[p0+1] = -(ax*by+ay*bx);
+                    p0 = p0+2;
+                    p1 = p1+2;
+                }
+                ftapplysubplan(plan, subplan, a, abase, aoffset+opidx*n*2, buf, 1, _params);
+                p0 = abase+aoffset+opidx*n*2;
+                for(i=0; i<=n-2; i++)
+                {
+                    a[p0+0] = a[p0+0]/(n-1);
+                    a[p0+1] = -(a[p0+1]/(n-1));
+                    p0 = p0+2;
+                }
+                
+                //
+                // Result
+                //
+                buf[aoffset+opidx*n*2+0] = rx;
+                buf[aoffset+opidx*n*2+1] = ry;
+                kiq = 1;
+                p0 = aoffset+opidx*n*2;
+                p1 = abase+aoffset+opidx*n*2;
+                for(q=0; q<=n-2; q++)
+                {
+                    buf[p0+2*kiq+0] = x0+a[p1+0];
+                    buf[p0+2*kiq+1] = y0+a[p1+1];
+                    kiq = kiq*riq%n;
+                    p1 = p1+2;
+                }
+                p0 = abase+aoffset+opidx*n*2;
+                p1 = aoffset+opidx*n*2;
+                for(q=0; q<=n-1; q++)
+                {
+                    a[p0] = buf[p1];
+                    a[p0+1] = buf[p1+1];
+                    p0 = p0+2;
+                    p1 = p1+2;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Factorizes task size N into product of two smaller sizes N1 and N2
+
+        INPUT PARAMETERS:
+            N       -   task size, N>0
+            IsRoot  -   whether taks is root task (first one in a sequence)
+            
+        OUTPUT PARAMETERS:
+            N1, N2  -   such numbers that:
+                        * for prime N:                  N1=N2=0
+                        * for composite N<=MaxRadix:    N1=N2=0
+                        * for composite N>MaxRadix:     1<=N1<=N2, N1*N2=N
+
+          -- ALGLIB --
+             Copyright 08.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ftfactorize(int n,
+            bool isroot,
+            ref int n1,
+            ref int n2,
+            alglib.xparams _params)
+        {
+            int j = 0;
+            int k = 0;
+
+            n1 = 0;
+            n2 = 0;
+
+            alglib.ap.assert(n>0, "FTFactorize: N<=0");
+            n1 = 0;
+            n2 = 0;
+            
+            //
+            // Small N
+            //
+            if( n<=maxradix )
+            {
+                return;
+            }
+            
+            //
+            // Large N, recursive split
+            //
+            if( n>recursivethreshold )
+            {
+                k = (int)Math.Ceiling(Math.Sqrt(n))+1;
+                alglib.ap.assert(k*k>=n, "FTFactorize: internal error during recursive factorization");
+                for(j=k; j>=2; j--)
+                {
+                    if( n%j==0 )
+                    {
+                        n1 = Math.Min(n/j, j);
+                        n2 = Math.Max(n/j, j);
+                        return;
+                    }
+                }
+            }
+            
+            //
+            // N>MaxRadix, try to find good codelet
+            //
+            for(j=maxradix; j>=2; j--)
+            {
+                if( n%j==0 )
+                {
+                    n1 = j;
+                    n2 = n/j;
+                    break;
+                }
+            }
+            
+            //
+            // In case no good codelet was found,
+            // try to factorize N into product of ANY primes.
+            //
+            if( n1*n2!=n )
+            {
+                for(j=2; j<=n-1; j++)
+                {
+                    if( n%j==0 )
+                    {
+                        n1 = j;
+                        n2 = n/j;
+                        break;
+                    }
+                    if( j*j>n )
+                    {
+                        break;
+                    }
+                }
+            }
+            
+            //
+            // normalize
+            //
+            if( n1>n2 )
+            {
+                j = n1;
+                n1 = n2;
+                n2 = j;
+            }
+        }
+
+
+        /*************************************************************************
+        Returns optimistic estimate of the FFT cost, in UNITs (1 UNIT = 100 KFLOPs)
+
+        INPUT PARAMETERS:
+            N       -   task size, N>0
+            
+        RESULU:
+            cost in UNITs, rounded down to nearest integer
+
+        NOTE: If FFT cost is less than 1 UNIT, it will return 0 as result.
+
+          -- ALGLIB --
+             Copyright 08.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static int ftoptimisticestimate(int n,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            alglib.ap.assert(n>0, "FTOptimisticEstimate: N<=0");
+            result = (int)Math.Floor(1.0E-5*5*n*Math.Log(n)/Math.Log(2));
+            return result;
+        }
+
+
+        /*************************************************************************
+        Twiddle factors calculation
+
+          -- ALGLIB --
+             Copyright 01.05.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ffttwcalc(double[] a,
+            int aoffset,
+            int n1,
+            int n2,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j2 = 0;
+            int n = 0;
+            int halfn1 = 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;
+            int updatetw2 = 0;
+
+            
+            //
+            // Multiplication by twiddle factors for complex Cooley-Tukey FFT
+            // with N factorized as N1*N2.
+            //
+            // Naive solution to this problem is given below:
+            //
+            //     > for K:=1 to N2-1 do
+            //     >     for J:=1 to N1-1 do
+            //     >     begin
+            //     >         Idx:=K*N1+J;
+            //     >         X:=A[AOffset+2*Idx+0];
+            //     >         Y:=A[AOffset+2*Idx+1];
+            //     >         TwX:=Cos(-2*Pi()*K*J/(N1*N2));
+            //     >         TwY:=Sin(-2*Pi()*K*J/(N1*N2));
+            //     >         A[AOffset+2*Idx+0]:=X*TwX-Y*TwY;
+            //     >         A[AOffset+2*Idx+1]:=X*TwY+Y*TwX;
+            //     >     end;
+            //
+            // However, there are exist more efficient solutions.
+            //
+            // Each pass of the inner cycle corresponds to multiplication of one
+            // entry of A by W[k,j]=exp(-I*2*pi*k*j/N). This factor can be rewritten
+            // as exp(-I*2*pi*k/N)^j. So we can replace costly exponentiation by
+            // repeated multiplication: W[k,j+1]=W[k,j]*exp(-I*2*pi*k/N), with
+            // second factor being computed once in the beginning of the iteration.
+            //
+            // Also, exp(-I*2*pi*k/N) can be represented as exp(-I*2*pi/N)^k, i.e.
+            // we have W[K+1,1]=W[K,1]*W[1,1].
+            //
+            // In our loop we use following variables:
+            // * [TwBaseXM1,TwBaseY] =   [cos(2*pi/N)-1,     sin(2*pi/N)]
+            // * [TwRowXM1, TwRowY]  =   [cos(2*pi*I/N)-1,   sin(2*pi*I/N)]
+            // * [TwXM1,    TwY]     =   [cos(2*pi*I*J/N)-1, sin(2*pi*I*J/N)]
+            //
+            // Meaning of the variables:
+            // * [TwXM1,TwY] is current twiddle factor W[I,J]
+            // * [TwRowXM1, TwRowY] is W[I,1]
+            // * [TwBaseXM1,TwBaseY] is W[1,1]
+            //
+            // During inner loop we multiply current twiddle factor by W[I,1],
+            // during outer loop we update W[I,1].
+            //
+            //
+            alglib.ap.assert(updatetw>=2, "FFTTwCalc: internal error - UpdateTw<2");
+            updatetw2 = updatetw/2;
+            halfn1 = n1/2;
+            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;
+            offs = aoffset;
+            for(i=0; i<=n2-1; i++)
+            {
+                
+                //
+                // Initialize twiddle factor for current row
+                //
+                twxm1 = 0;
+                twy = 0;
+                
+                //
+                // N1-point block is separated into 2-point chunks and residual 1-point chunk
+                // (in case N1 is odd). Unrolled loop is several times faster.
+                //
+                for(j2=0; j2<=halfn1-1; j2++)
+                {
+                    
+                    //
+                    // Processing:
+                    // * process first element in a chunk.
+                    // * update twiddle factor (unconditional update)
+                    // * process second element
+                    // * conditional update of the twiddle factor
+                    //
+                    x = a[offs+0];
+                    y = a[offs+1];
+                    tmpx = x*(1+twxm1)-y*twy;
+                    tmpy = x*twy+y*(1+twxm1);
+                    a[offs+0] = tmpx;
+                    a[offs+1] = tmpy;
+                    tmpx = (1+twxm1)*twrowxm1-twy*twrowy;
+                    twy = twy+(1+twxm1)*twrowy+twy*twrowxm1;
+                    twxm1 = twxm1+tmpx;
+                    x = a[offs+2];
+                    y = a[offs+3];
+                    tmpx = x*(1+twxm1)-y*twy;
+                    tmpy = x*twy+y*(1+twxm1);
+                    a[offs+2] = tmpx;
+                    a[offs+3] = tmpy;
+                    offs = offs+4;
+                    if( (j2+1)%updatetw2==0 && j2<halfn1-1 )
+                    {
+                        
+                        //
+                        // Recalculate twiddle factor
+                        //
+                        v = -(2*Math.PI*i*2*(j2+1)/n);
+                        twxm1 = Math.Sin(0.5*v);
+                        twxm1 = -(2*twxm1*twxm1);
+                        twy = Math.Sin(v);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Update twiddle factor
+                        //
+                        tmpx = (1+twxm1)*twrowxm1-twy*twrowy;
+                        twy = twy+(1+twxm1)*twrowy+twy*twrowxm1;
+                        twxm1 = twxm1+tmpx;
+                    }
+                }
+                if( n1%2==1 )
+                {
+                    
+                    //
+                    // Handle residual chunk
+                    //
+                    x = a[offs+0];
+                    y = a[offs+1];
+                    tmpx = x*(1+twxm1)-y*twy;
+                    tmpy = x*twy+y*(1+twxm1);
+                    a[offs+0] = tmpx;
+                    a[offs+1] = tmpy;
+                    offs = offs+2;
+                }
+                
+                //
+                // update TwRow: TwRow(new) = TwRow(old)*TwBase
+                //
+                if( i<n2-1 )
+                {
+                    if( (i+1)%updatetw==0 )
+                    {
+                        v = -(2*Math.PI*(i+1)/n);
+                        twrowxm1 = Math.Sin(0.5*v);
+                        twrowxm1 = -(2*twrowxm1*twrowxm1);
+                        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(double[] a,
+            int m,
+            int n,
+            int astart,
+            double[] buf,
+            alglib.xparams _params)
+        {
+            int i_ = 0;
+            int i1_ = 0;
+
+            ffticltrec(a, astart, n, buf, 0, m, m, n, _params);
+            i1_ = (0) - (astart);
+            for(i_=astart; i_<=astart+2*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(double[] a,
+            int astart,
+            int astride,
+            double[] b,
+            int bstart,
+            int bstride,
+            int m,
+            int n,
+            alglib.xparams _params)
+        {
+            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(a, astart, astride, b, bstart, bstride, m, n1, _params);
+                ffticltrec(a, astart+2*n1, astride, b, bstart+2*n1*bstride, bstride, m, n-n1, _params);
+            }
+            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(a, astart, astride, b, bstart, bstride, m1, n, _params);
+                ffticltrec(a, astart+2*m1*astride, astride, b, bstart+2*m1, bstride, m-m1, n, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                fftirltrec(ref a, astart+n1, astride, ref b, bstart+n1*bstride, bstride, m, n-n1, _params);
+            }
+            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, _params);
+                fftirltrec(ref a, astart+m1*astride, astride, ref b, bstart+m1, bstride, m-m1, n, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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, _params);
+            }
+            if( leastfactor<=3 )
+            {
+                ftbasefindsmoothrec(n, seed*3, 3, ref best, _params);
+            }
+            if( leastfactor<=5 )
+            {
+                ftbasefindsmoothrec(n, seed*5, 5, ref best, _params);
+            }
+        }
+
+
+    }
+    public class nearunityunit
+    {
+        public static double nulog1p(double x,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/alglibmisc.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/alglibmisc.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/alglibmisc.cs	(revision 17158)
@@ -0,0 +1,6916 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 164
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Buffer object which is used to perform nearest neighbor  requests  in  the
+    multithreaded mode (multiple threads working with same KD-tree object).
+
+    This object should be created with KDTreeCreateRequestBuffer().
+    *************************************************************************/
+    public class kdtreerequestbuffer : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public kdtreerequestbuffer()
+        {
+            _innerobj = new nearestneighbor.kdtreerequestbuffer();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new kdtreerequestbuffer((nearestneighbor.kdtreerequestbuffer)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private nearestneighbor.kdtreerequestbuffer _innerobj;
+        public nearestneighbor.kdtreerequestbuffer innerobj { get { return _innerobj; } }
+        public kdtreerequestbuffer(nearestneighbor.kdtreerequestbuffer obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    KD-tree object.
+    *************************************************************************/
+    public class kdtree : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public kdtree()
+        {
+            _innerobj = new nearestneighbor.kdtree();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new kdtree((nearestneighbor.kdtree)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+        s.sstart_str();
+        nearestneighbor.kdtreeserialize(s, obj.innerobj, null);
+        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, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to stream.
+    
+    Data stream generated by this function is same as  string  representation
+    generated  by  string  version  of  serializer - alphanumeric characters,
+    dots, underscores, minus signs, which are grouped into words separated by
+    spaces and CR+LF.
+    
+    We recommend you to read comments on string version of serializer to find
+    out more about serialization of AlGLIB objects.
+    *************************************************************************/
+    public static void kdtreeserialize(kdtree obj, System.IO.Stream stream_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        nearestneighbor.kdtreealloc(s, obj.innerobj, null);
+        s.sstart_stream(stream_out);
+        nearestneighbor.kdtreeserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from stream.
+    *************************************************************************/
+    public static void kdtreeunserialize(System.IO.Stream stream_in, out kdtree obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new kdtree();
+        s.ustart_stream(stream_in);
+        nearestneighbor.kdtreeunserialize(s, obj.innerobj, null);
+        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, null);
+    }
+    
+    public static void kdtreebuild(double[,] xy, int n, int nx, int ny, int normtype, out kdtree kdt, alglib.xparams _params)
+    {
+        kdt = new kdtree();
+        nearestneighbor.kdtreebuild(xy, n, nx, ny, normtype, kdt.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void kdtreebuild(double[,] xy, int nx, int ny, int normtype, out kdtree kdt, alglib.xparams _params)
+    {
+        int n;
+    
+        kdt = new kdtree();
+        n = ap.rows(xy);
+        nearestneighbor.kdtreebuild(xy, n, nx, ny, normtype, kdt.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void kdtreebuildtagged(double[,] xy, int[] tags, int n, int nx, int ny, int normtype, out kdtree kdt, alglib.xparams _params)
+    {
+        kdt = new kdtree();
+        nearestneighbor.kdtreebuildtagged(xy, tags, n, nx, ny, normtype, kdt.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void kdtreebuildtagged(double[,] xy, int[] tags, int nx, int ny, int normtype, out kdtree kdt, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function creates buffer  structure  which  can  be  used  to  perform
+    parallel KD-tree requests.
+
+    KD-tree subpackage provides two sets of request functions - ones which use
+    internal buffer of KD-tree object  (these  functions  are  single-threaded
+    because they use same buffer, which can not shared between  threads),  and
+    ones which use external buffer.
+
+    This function is used to initialize external buffer.
+
+    INPUT PARAMETERS
+        KDT         -   KD-tree which is associated with newly created buffer
+
+    OUTPUT PARAMETERS
+        Buf         -   external buffer.
+
+
+    IMPORTANT: KD-tree buffer should be used only with  KD-tree  object  which
+               was used to initialize buffer. Any attempt to use buffer   with
+               different object is dangerous - you  may  get  integrity  check
+               failure (exception) because sizes of internal arrays do not fit
+               to dimensions of KD-tree structure.
+
+      -- ALGLIB --
+         Copyright 18.03.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static void kdtreecreaterequestbuffer(kdtree kdt, out kdtreerequestbuffer buf)
+    {
+        buf = new kdtreerequestbuffer();
+        nearestneighbor.kdtreecreaterequestbuffer(kdt.innerobj, buf.innerobj, null);
+    }
+    
+    public static void kdtreecreaterequestbuffer(kdtree kdt, out kdtreerequestbuffer buf, alglib.xparams _params)
+    {
+        buf = new kdtreerequestbuffer();
+        nearestneighbor.kdtreecreaterequestbuffer(kdt.innerobj, buf.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    K-NN query: K nearest neighbors
+
+    IMPORTANT: this function can not be used in multithreaded code because  it
+               uses internal temporary buffer of kd-tree object, which can not
+               be shared between multiple threads.  If  you  want  to  perform
+               parallel requests, use function  which  uses  external  request
+               buffer: KDTreeTsQueryKNN() ("Ts" stands for "thread-safe").
+
+    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)
+    {
+    
+        return nearestneighbor.kdtreequeryknn(kdt.innerobj, x, k, selfmatch, null);
+    }
+    
+    public static int kdtreequeryknn(kdtree kdt, double[] x, int k, bool selfmatch, alglib.xparams _params)
+    {
+    
+        return nearestneighbor.kdtreequeryknn(kdt.innerobj, x, k, selfmatch, _params);
+    }
+            
+    public static int kdtreequeryknn(kdtree kdt, double[] x, int k)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreequeryknn(kdt.innerobj, x, k, selfmatch, null);
+    
+        return result;
+    }
+            
+    public static int kdtreequeryknn(kdtree kdt, double[] x, int k, alglib.xparams _params)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreequeryknn(kdt.innerobj, x, k, selfmatch, _params);
+    
+        return result;
+    }
+    
+    /*************************************************************************
+    K-NN query: K nearest neighbors, using external thread-local buffer.
+
+    You can call this function from multiple threads for same kd-tree instance,
+    assuming that different instances of buffer object are passed to different
+    threads.
+
+    INPUT PARAMETERS
+        KDT         -   kd-tree
+        Buf         -   request buffer  object  created  for  this  particular
+                        instance of kd-tree structure with kdtreecreaterequestbuffer()
+                        function.
+        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  buffer object. You can use following  subroutines  to
+    obtain these results (pay attention to "buf" in their names):
+    * KDTreeTsQueryResultsX() to get X-values
+    * KDTreeTsQueryResultsXY() to get X- and Y-values
+    * KDTreeTsQueryResultsTags() to get tag values
+    * KDTreeTsQueryResultsDistances() to get distances
+
+    IMPORTANT: kd-tree buffer should be used only with  KD-tree  object  which
+               was used to initialize buffer. Any attempt to use biffer   with
+               different object is dangerous - you  may  get  integrity  check
+               failure (exception) because sizes of internal arrays do not fit
+               to dimensions of KD-tree structure.
+
+      -- ALGLIB --
+         Copyright 18.03.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static int kdtreetsqueryknn(kdtree kdt, kdtreerequestbuffer buf, double[] x, int k, bool selfmatch)
+    {
+    
+        return nearestneighbor.kdtreetsqueryknn(kdt.innerobj, buf.innerobj, x, k, selfmatch, null);
+    }
+    
+    public static int kdtreetsqueryknn(kdtree kdt, kdtreerequestbuffer buf, double[] x, int k, bool selfmatch, alglib.xparams _params)
+    {
+    
+        return nearestneighbor.kdtreetsqueryknn(kdt.innerobj, buf.innerobj, x, k, selfmatch, _params);
+    }
+            
+    public static int kdtreetsqueryknn(kdtree kdt, kdtreerequestbuffer buf, double[] x, int k)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreetsqueryknn(kdt.innerobj, buf.innerobj, x, k, selfmatch, null);
+    
+        return result;
+    }
+            
+    public static int kdtreetsqueryknn(kdtree kdt, kdtreerequestbuffer buf, double[] x, int k, alglib.xparams _params)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreetsqueryknn(kdt.innerobj, buf.innerobj, x, k, selfmatch, _params);
+    
+        return result;
+    }
+    
+    /*************************************************************************
+    R-NN query: all points within R-sphere centered at X, ordered by  distance
+    between point and X (by ascending).
+
+    NOTE: it is also possible to perform undordered queries performed by means
+          of kdtreequeryrnnu() and kdtreetsqueryrnnu() functions. Such queries
+          are faster because we do not have to use heap structure for sorting.
+
+    IMPORTANT: this function can not be used in multithreaded code because  it
+               uses internal temporary buffer of kd-tree object, which can not
+               be shared between multiple threads.  If  you  want  to  perform
+               parallel requests, use function  which  uses  external  request
+               buffer: kdtreetsqueryrnn() ("Ts" stands for "thread-safe").
+
+    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)
+    {
+    
+        return nearestneighbor.kdtreequeryrnn(kdt.innerobj, x, r, selfmatch, null);
+    }
+    
+    public static int kdtreequeryrnn(kdtree kdt, double[] x, double r, bool selfmatch, alglib.xparams _params)
+    {
+    
+        return nearestneighbor.kdtreequeryrnn(kdt.innerobj, x, r, selfmatch, _params);
+    }
+            
+    public static int kdtreequeryrnn(kdtree kdt, double[] x, double r)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreequeryrnn(kdt.innerobj, x, r, selfmatch, null);
+    
+        return result;
+    }
+            
+    public static int kdtreequeryrnn(kdtree kdt, double[] x, double r, alglib.xparams _params)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreequeryrnn(kdt.innerobj, x, r, selfmatch, _params);
+    
+        return result;
+    }
+    
+    /*************************************************************************
+    R-NN query: all points within R-sphere  centered  at  X,  no  ordering  by
+    distance as undicated by "U" suffix (faster that ordered query, for  large
+    queries - significantly faster).
+
+    IMPORTANT: this function can not be used in multithreaded code because  it
+               uses internal temporary buffer of kd-tree object, which can not
+               be shared between multiple threads.  If  you  want  to  perform
+               parallel requests, use function  which  uses  external  request
+               buffer: kdtreetsqueryrnn() ("Ts" stands for "thread-safe").
+
+    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
+
+    As indicated by "U" suffix, this function returns unordered results.
+
+      -- ALGLIB --
+         Copyright 01.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static int kdtreequeryrnnu(kdtree kdt, double[] x, double r, bool selfmatch)
+    {
+    
+        return nearestneighbor.kdtreequeryrnnu(kdt.innerobj, x, r, selfmatch, null);
+    }
+    
+    public static int kdtreequeryrnnu(kdtree kdt, double[] x, double r, bool selfmatch, alglib.xparams _params)
+    {
+    
+        return nearestneighbor.kdtreequeryrnnu(kdt.innerobj, x, r, selfmatch, _params);
+    }
+            
+    public static int kdtreequeryrnnu(kdtree kdt, double[] x, double r)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreequeryrnnu(kdt.innerobj, x, r, selfmatch, null);
+    
+        return result;
+    }
+            
+    public static int kdtreequeryrnnu(kdtree kdt, double[] x, double r, alglib.xparams _params)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreequeryrnnu(kdt.innerobj, x, r, selfmatch, _params);
+    
+        return result;
+    }
+    
+    /*************************************************************************
+    R-NN query: all points within  R-sphere  centered  at  X,  using  external
+    thread-local buffer, sorted by distance between point and X (by ascending)
+
+    You can call this function from multiple threads for same kd-tree instance,
+    assuming that different instances of buffer object are passed to different
+    threads.
+
+    NOTE: it is also possible to perform undordered queries performed by means
+          of kdtreequeryrnnu() and kdtreetsqueryrnnu() functions. Such queries
+          are faster because we do not have to use heap structure for sorting.
+
+    INPUT PARAMETERS
+        KDT         -   KD-tree
+        Buf         -   request buffer  object  created  for  this  particular
+                        instance of kd-tree structure with kdtreecreaterequestbuffer()
+                        function.
+        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  buffer object. You can use following  subroutines  to
+    obtain these results (pay attention to "buf" in their names):
+    * KDTreeTsQueryResultsX() to get X-values
+    * KDTreeTsQueryResultsXY() to get X- and Y-values
+    * KDTreeTsQueryResultsTags() to get tag values
+    * KDTreeTsQueryResultsDistances() to get distances
+
+    IMPORTANT: kd-tree buffer should be used only with  KD-tree  object  which
+               was used to initialize buffer. Any attempt to use biffer   with
+               different object is dangerous - you  may  get  integrity  check
+               failure (exception) because sizes of internal arrays do not fit
+               to dimensions of KD-tree structure.
+
+      -- ALGLIB --
+         Copyright 18.03.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static int kdtreetsqueryrnn(kdtree kdt, kdtreerequestbuffer buf, double[] x, double r, bool selfmatch)
+    {
+    
+        return nearestneighbor.kdtreetsqueryrnn(kdt.innerobj, buf.innerobj, x, r, selfmatch, null);
+    }
+    
+    public static int kdtreetsqueryrnn(kdtree kdt, kdtreerequestbuffer buf, double[] x, double r, bool selfmatch, alglib.xparams _params)
+    {
+    
+        return nearestneighbor.kdtreetsqueryrnn(kdt.innerobj, buf.innerobj, x, r, selfmatch, _params);
+    }
+            
+    public static int kdtreetsqueryrnn(kdtree kdt, kdtreerequestbuffer buf, double[] x, double r)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreetsqueryrnn(kdt.innerobj, buf.innerobj, x, r, selfmatch, null);
+    
+        return result;
+    }
+            
+    public static int kdtreetsqueryrnn(kdtree kdt, kdtreerequestbuffer buf, double[] x, double r, alglib.xparams _params)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreetsqueryrnn(kdt.innerobj, buf.innerobj, x, r, selfmatch, _params);
+    
+        return result;
+    }
+    
+    /*************************************************************************
+    R-NN query: all points within  R-sphere  centered  at  X,  using  external
+    thread-local buffer, no ordering by distance as undicated  by  "U"  suffix
+    (faster that ordered query, for large queries - significantly faster).
+
+    You can call this function from multiple threads for same kd-tree instance,
+    assuming that different instances of buffer object are passed to different
+    threads.
+
+    INPUT PARAMETERS
+        KDT         -   KD-tree
+        Buf         -   request buffer  object  created  for  this  particular
+                        instance of kd-tree structure with kdtreecreaterequestbuffer()
+                        function.
+        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  buffer object. You can use following  subroutines  to
+    obtain these results (pay attention to "buf" in their names):
+    * KDTreeTsQueryResultsX() to get X-values
+    * KDTreeTsQueryResultsXY() to get X- and Y-values
+    * KDTreeTsQueryResultsTags() to get tag values
+    * KDTreeTsQueryResultsDistances() to get distances
+
+    As indicated by "U" suffix, this function returns unordered results.
+
+    IMPORTANT: kd-tree buffer should be used only with  KD-tree  object  which
+               was used to initialize buffer. Any attempt to use biffer   with
+               different object is dangerous - you  may  get  integrity  check
+               failure (exception) because sizes of internal arrays do not fit
+               to dimensions of KD-tree structure.
+
+      -- ALGLIB --
+         Copyright 18.03.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static int kdtreetsqueryrnnu(kdtree kdt, kdtreerequestbuffer buf, double[] x, double r, bool selfmatch)
+    {
+    
+        return nearestneighbor.kdtreetsqueryrnnu(kdt.innerobj, buf.innerobj, x, r, selfmatch, null);
+    }
+    
+    public static int kdtreetsqueryrnnu(kdtree kdt, kdtreerequestbuffer buf, double[] x, double r, bool selfmatch, alglib.xparams _params)
+    {
+    
+        return nearestneighbor.kdtreetsqueryrnnu(kdt.innerobj, buf.innerobj, x, r, selfmatch, _params);
+    }
+            
+    public static int kdtreetsqueryrnnu(kdtree kdt, kdtreerequestbuffer buf, double[] x, double r)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreetsqueryrnnu(kdt.innerobj, buf.innerobj, x, r, selfmatch, null);
+    
+        return result;
+    }
+            
+    public static int kdtreetsqueryrnnu(kdtree kdt, kdtreerequestbuffer buf, double[] x, double r, alglib.xparams _params)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreetsqueryrnnu(kdt.innerobj, buf.innerobj, x, r, selfmatch, _params);
+    
+        return result;
+    }
+    
+    /*************************************************************************
+    K-NN query: approximate K nearest neighbors
+
+    IMPORTANT: this function can not be used in multithreaded code because  it
+               uses internal temporary buffer of kd-tree object, which can not
+               be shared between multiple threads.  If  you  want  to  perform
+               parallel requests, use function  which  uses  external  request
+               buffer: KDTreeTsQueryAKNN() ("Ts" stands for "thread-safe").
+
+    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)
+    {
+    
+        return nearestneighbor.kdtreequeryaknn(kdt.innerobj, x, k, selfmatch, eps, null);
+    }
+    
+    public static int kdtreequeryaknn(kdtree kdt, double[] x, int k, bool selfmatch, double eps, alglib.xparams _params)
+    {
+    
+        return nearestneighbor.kdtreequeryaknn(kdt.innerobj, x, k, selfmatch, eps, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static int kdtreequeryaknn(kdtree kdt, double[] x, int k, double eps, alglib.xparams _params)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreequeryaknn(kdt.innerobj, x, k, selfmatch, eps, _params);
+    
+        return result;
+    }
+    
+    /*************************************************************************
+    K-NN query: approximate K nearest neighbors, using thread-local buffer.
+
+    You can call this function from multiple threads for same kd-tree instance,
+    assuming that different instances of buffer object are passed to different
+    threads.
+
+    INPUT PARAMETERS
+        KDT         -   KD-tree
+        Buf         -   request buffer  object  created  for  this  particular
+                        instance of kd-tree structure with kdtreecreaterequestbuffer()
+                        function.
+        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  buffer object. You can use following  subroutines  to
+    obtain these results (pay attention to "buf" in their names):
+    * KDTreeTsQueryResultsX() to get X-values
+    * KDTreeTsQueryResultsXY() to get X- and Y-values
+    * KDTreeTsQueryResultsTags() to get tag values
+    * KDTreeTsQueryResultsDistances() to get distances
+
+    IMPORTANT: kd-tree buffer should be used only with  KD-tree  object  which
+               was used to initialize buffer. Any attempt to use biffer   with
+               different object is dangerous - you  may  get  integrity  check
+               failure (exception) because sizes of internal arrays do not fit
+               to dimensions of KD-tree structure.
+
+      -- ALGLIB --
+         Copyright 18.03.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static int kdtreetsqueryaknn(kdtree kdt, kdtreerequestbuffer buf, double[] x, int k, bool selfmatch, double eps)
+    {
+    
+        return nearestneighbor.kdtreetsqueryaknn(kdt.innerobj, buf.innerobj, x, k, selfmatch, eps, null);
+    }
+    
+    public static int kdtreetsqueryaknn(kdtree kdt, kdtreerequestbuffer buf, double[] x, int k, bool selfmatch, double eps, alglib.xparams _params)
+    {
+    
+        return nearestneighbor.kdtreetsqueryaknn(kdt.innerobj, buf.innerobj, x, k, selfmatch, eps, _params);
+    }
+            
+    public static int kdtreetsqueryaknn(kdtree kdt, kdtreerequestbuffer buf, double[] x, int k, double eps)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreetsqueryaknn(kdt.innerobj, buf.innerobj, x, k, selfmatch, eps, null);
+    
+        return result;
+    }
+            
+    public static int kdtreetsqueryaknn(kdtree kdt, kdtreerequestbuffer buf, double[] x, int k, double eps, alglib.xparams _params)
+    {
+        bool selfmatch;
+    
+    
+        selfmatch = true;
+        int result = nearestneighbor.kdtreetsqueryaknn(kdt.innerobj, buf.innerobj, x, k, selfmatch, eps, _params);
+    
+        return result;
+    }
+    
+    /*************************************************************************
+    Box query: all points within user-specified box.
+
+    IMPORTANT: this function can not be used in multithreaded code because  it
+               uses internal temporary buffer of kd-tree object, which can not
+               be shared between multiple threads.  If  you  want  to  perform
+               parallel requests, use function  which  uses  external  request
+               buffer: KDTreeTsQueryBox() ("Ts" stands for "thread-safe").
+
+    INPUT PARAMETERS
+        KDT         -   KD-tree
+        BoxMin      -   lower bounds, array[0..NX-1].
+        BoxMax      -   upper bounds, array[0..NX-1].
+
+
+    RESULT
+        number of actual neighbors found (in [0,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() returns zeros for this request
+
+    NOTE: this particular query returns unordered results, because there is no
+          meaningful way of  ordering  points.  Furthermore,  no 'distance' is
+          associated with points - it is either INSIDE  or OUTSIDE (so request
+          for distances will return zeros).
+
+      -- ALGLIB --
+         Copyright 14.05.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static int kdtreequerybox(kdtree kdt, double[] boxmin, double[] boxmax)
+    {
+    
+        return nearestneighbor.kdtreequerybox(kdt.innerobj, boxmin, boxmax, null);
+    }
+    
+    public static int kdtreequerybox(kdtree kdt, double[] boxmin, double[] boxmax, alglib.xparams _params)
+    {
+    
+        return nearestneighbor.kdtreequerybox(kdt.innerobj, boxmin, boxmax, _params);
+    }
+    
+    /*************************************************************************
+    Box query: all points within user-specified box, using thread-local buffer.
+
+    You can call this function from multiple threads for same kd-tree instance,
+    assuming that different instances of buffer object are passed to different
+    threads.
+
+    INPUT PARAMETERS
+        KDT         -   KD-tree
+        Buf         -   request buffer  object  created  for  this  particular
+                        instance of kd-tree structure with kdtreecreaterequestbuffer()
+                        function.
+        BoxMin      -   lower bounds, array[0..NX-1].
+        BoxMax      -   upper bounds, array[0..NX-1].
+
+    RESULT
+        number of actual neighbors found (in [0,N]).
+
+    This  subroutine  performs  query  and  stores  its result in the internal
+    structures  of  the  buffer object. You can use following  subroutines  to
+    obtain these results (pay attention to "ts" in their names):
+    * KDTreeTsQueryResultsX() to get X-values
+    * KDTreeTsQueryResultsXY() to get X- and Y-values
+    * KDTreeTsQueryResultsTags() to get tag values
+    * KDTreeTsQueryResultsDistances() returns zeros for this query
+
+    NOTE: this particular query returns unordered results, because there is no
+          meaningful way of  ordering  points.  Furthermore,  no 'distance' is
+          associated with points - it is either INSIDE  or OUTSIDE (so request
+          for distances will return zeros).
+
+    IMPORTANT: kd-tree buffer should be used only with  KD-tree  object  which
+               was used to initialize buffer. Any attempt to use biffer   with
+               different object is dangerous - you  may  get  integrity  check
+               failure (exception) because sizes of internal arrays do not fit
+               to dimensions of KD-tree structure.
+
+      -- ALGLIB --
+         Copyright 14.05.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static int kdtreetsquerybox(kdtree kdt, kdtreerequestbuffer buf, double[] boxmin, double[] boxmax)
+    {
+    
+        return nearestneighbor.kdtreetsquerybox(kdt.innerobj, buf.innerobj, boxmin, boxmax, null);
+    }
+    
+    public static int kdtreetsquerybox(kdtree kdt, kdtreerequestbuffer buf, double[] boxmin, double[] boxmax, alglib.xparams _params)
+    {
+    
+        return nearestneighbor.kdtreetsquerybox(kdt.innerobj, buf.innerobj, boxmin, boxmax, _params);
+    }
+    
+    /*************************************************************************
+    X-values from last query.
+
+    This function retuns results stored in  the  internal  buffer  of  kd-tree
+    object. If you performed buffered requests (ones which  use  instances  of
+    kdtreerequestbuffer class), you  should  call  buffered  version  of  this
+    function - kdtreetsqueryresultsx().
+
+    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, null);
+    }
+    
+    public static void kdtreequeryresultsx(kdtree kdt, ref double[,] x, alglib.xparams _params)
+    {
+    
+        nearestneighbor.kdtreequeryresultsx(kdt.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    X- and Y-values from last query
+
+    This function retuns results stored in  the  internal  buffer  of  kd-tree
+    object. If you performed buffered requests (ones which  use  instances  of
+    kdtreerequestbuffer class), you  should  call  buffered  version  of  this
+    function - kdtreetsqueryresultsxy().
+
+    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, null);
+    }
+    
+    public static void kdtreequeryresultsxy(kdtree kdt, ref double[,] xy, alglib.xparams _params)
+    {
+    
+        nearestneighbor.kdtreequeryresultsxy(kdt.innerobj, ref xy, _params);
+    }
+    
+    /*************************************************************************
+    Tags from last query
+
+    This function retuns results stored in  the  internal  buffer  of  kd-tree
+    object. If you performed buffered requests (ones which  use  instances  of
+    kdtreerequestbuffer class), you  should  call  buffered  version  of  this
+    function - kdtreetsqueryresultstags().
+
+    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, null);
+    }
+    
+    public static void kdtreequeryresultstags(kdtree kdt, ref int[] tags, alglib.xparams _params)
+    {
+    
+        nearestneighbor.kdtreequeryresultstags(kdt.innerobj, ref tags, _params);
+    }
+    
+    /*************************************************************************
+    Distances from last query
+
+    This function retuns results stored in  the  internal  buffer  of  kd-tree
+    object. If you performed buffered requests (ones which  use  instances  of
+    kdtreerequestbuffer class), you  should  call  buffered  version  of  this
+    function - kdtreetsqueryresultsdistances().
+
+    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, null);
+    }
+    
+    public static void kdtreequeryresultsdistances(kdtree kdt, ref double[] r, alglib.xparams _params)
+    {
+    
+        nearestneighbor.kdtreequeryresultsdistances(kdt.innerobj, ref r, _params);
+    }
+    
+    /*************************************************************************
+    X-values from last query associated with kdtreerequestbuffer object.
+
+    INPUT PARAMETERS
+        KDT     -   KD-tree
+        Buf     -   request  buffer  object  created   for   this   particular
+                    instance of kd-tree structure.
+        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 kdtreetsqueryresultsx(kdtree kdt, kdtreerequestbuffer buf, ref double[,] x)
+    {
+    
+        nearestneighbor.kdtreetsqueryresultsx(kdt.innerobj, buf.innerobj, ref x, null);
+    }
+    
+    public static void kdtreetsqueryresultsx(kdtree kdt, kdtreerequestbuffer buf, ref double[,] x, alglib.xparams _params)
+    {
+    
+        nearestneighbor.kdtreetsqueryresultsx(kdt.innerobj, buf.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    X- and Y-values from last query associated with kdtreerequestbuffer object.
+
+    INPUT PARAMETERS
+        KDT     -   KD-tree
+        Buf     -   request  buffer  object  created   for   this   particular
+                    instance of kd-tree structure.
+        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 kdtreetsqueryresultsxy(kdtree kdt, kdtreerequestbuffer buf, ref double[,] xy)
+    {
+    
+        nearestneighbor.kdtreetsqueryresultsxy(kdt.innerobj, buf.innerobj, ref xy, null);
+    }
+    
+    public static void kdtreetsqueryresultsxy(kdtree kdt, kdtreerequestbuffer buf, ref double[,] xy, alglib.xparams _params)
+    {
+    
+        nearestneighbor.kdtreetsqueryresultsxy(kdt.innerobj, buf.innerobj, ref xy, _params);
+    }
+    
+    /*************************************************************************
+    Tags from last query associated with kdtreerequestbuffer object.
+
+    This function retuns results stored in  the  internal  buffer  of  kd-tree
+    object. If you performed buffered requests (ones which  use  instances  of
+    kdtreerequestbuffer class), you  should  call  buffered  version  of  this
+    function - KDTreeTsqueryresultstags().
+
+    INPUT PARAMETERS
+        KDT     -   KD-tree
+        Buf     -   request  buffer  object  created   for   this   particular
+                    instance of kd-tree structure.
+        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 kdtreetsqueryresultstags(kdtree kdt, kdtreerequestbuffer buf, ref int[] tags)
+    {
+    
+        nearestneighbor.kdtreetsqueryresultstags(kdt.innerobj, buf.innerobj, ref tags, null);
+    }
+    
+    public static void kdtreetsqueryresultstags(kdtree kdt, kdtreerequestbuffer buf, ref int[] tags, alglib.xparams _params)
+    {
+    
+        nearestneighbor.kdtreetsqueryresultstags(kdt.innerobj, buf.innerobj, ref tags, _params);
+    }
+    
+    /*************************************************************************
+    Distances from last query associated with kdtreerequestbuffer object.
+
+    This function retuns results stored in  the  internal  buffer  of  kd-tree
+    object. If you performed buffered requests (ones which  use  instances  of
+    kdtreerequestbuffer class), you  should  call  buffered  version  of  this
+    function - KDTreeTsqueryresultsdistances().
+
+    INPUT PARAMETERS
+        KDT     -   KD-tree
+        Buf     -   request  buffer  object  created   for   this   particular
+                    instance of kd-tree structure.
+        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 kdtreetsqueryresultsdistances(kdtree kdt, kdtreerequestbuffer buf, ref double[] r)
+    {
+    
+        nearestneighbor.kdtreetsqueryresultsdistances(kdt.innerobj, buf.innerobj, ref r, null);
+    }
+    
+    public static void kdtreetsqueryresultsdistances(kdtree kdt, kdtreerequestbuffer buf, ref double[] r, alglib.xparams _params)
+    {
+    
+        nearestneighbor.kdtreetsqueryresultsdistances(kdt.innerobj, buf.innerobj, ref r, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void kdtreequeryresultsxi(kdtree kdt, out double[,] x, alglib.xparams _params)
+    {
+        x = new double[0,0];
+        nearestneighbor.kdtreequeryresultsxi(kdt.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void kdtreequeryresultsxyi(kdtree kdt, out double[,] xy, alglib.xparams _params)
+    {
+        xy = new double[0,0];
+        nearestneighbor.kdtreequeryresultsxyi(kdt.innerobj, ref xy, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void kdtreequeryresultstagsi(kdtree kdt, out int[] tags, alglib.xparams _params)
+    {
+        tags = new int[0];
+        nearestneighbor.kdtreequeryresultstagsi(kdt.innerobj, ref tags, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void kdtreequeryresultsdistancesi(kdtree kdt, out double[] r, alglib.xparams _params)
+    {
+        r = new double[0];
+        nearestneighbor.kdtreequeryresultsdistancesi(kdt.innerobj, ref r, _params);
+    }
+
+}
+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 : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public hqrndstate()
+        {
+            _innerobj = new hqrnd.hqrndstate();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new hqrndstate((hqrnd.hqrndstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void hqrndrandomize(out hqrndstate state, alglib.xparams _params)
+    {
+        state = new hqrndstate();
+        hqrnd.hqrndrandomize(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void hqrndseed(int s1, int s2, out hqrndstate state, alglib.xparams _params)
+    {
+        state = new hqrndstate();
+        hqrnd.hqrndseed(s1, s2, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return hqrnd.hqrnduniformr(state.innerobj, null);
+    }
+    
+    public static double hqrnduniformr(hqrndstate state, alglib.xparams _params)
+    {
+    
+        return hqrnd.hqrnduniformr(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function generates random integer number in [0, N)
+
+    1. State structure must be initialized with HQRNDRandomize() or HQRNDSeed()
+    2. N can be any positive number except for very large numbers:
+       * close to 2^31 on 32-bit systems
+       * close to 2^62 on 64-bit systems
+       An exception will be generated if N is too large.
+
+      -- ALGLIB --
+         Copyright 02.12.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static int hqrnduniformi(hqrndstate state, int n)
+    {
+    
+        return hqrnd.hqrnduniformi(state.innerobj, n, null);
+    }
+    
+    public static int hqrnduniformi(hqrndstate state, int n, alglib.xparams _params)
+    {
+    
+        return hqrnd.hqrnduniformi(state.innerobj, n, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return hqrnd.hqrndnormal(state.innerobj, null);
+    }
+    
+    public static double hqrndnormal(hqrndstate state, alglib.xparams _params)
+    {
+    
+        return hqrnd.hqrndnormal(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void hqrndunit2(hqrndstate state, out double x, out double y, alglib.xparams _params)
+    {
+        x = 0;
+        y = 0;
+        hqrnd.hqrndunit2(state.innerobj, ref x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void hqrndnormal2(hqrndstate state, out double x1, out double x2, alglib.xparams _params)
+    {
+        x1 = 0;
+        x2 = 0;
+        hqrnd.hqrndnormal2(state.innerobj, ref x1, ref x2, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return hqrnd.hqrndexponential(state.innerobj, lambdav, null);
+    }
+    
+    public static double hqrndexponential(hqrndstate state, double lambdav, alglib.xparams _params)
+    {
+    
+        return hqrnd.hqrndexponential(state.innerobj, lambdav, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return hqrnd.hqrnddiscrete(state.innerobj, x, n, null);
+    }
+    
+    public static double hqrnddiscrete(hqrndstate state, double[] x, int n, alglib.xparams _params)
+    {
+    
+        return hqrnd.hqrnddiscrete(state.innerobj, x, n, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return hqrnd.hqrndcontinuous(state.innerobj, x, n, null);
+    }
+    
+    public static double hqrndcontinuous(hqrndstate state, double[] x, int n, alglib.xparams _params)
+    {
+    
+        return hqrnd.hqrndcontinuous(state.innerobj, x, n, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class xdebugrecord1 : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public int i { get { return _innerobj.i; } set { _innerobj.i = value; } }
+        public complex c { get { return _innerobj.c; } set { _innerobj.c = value; } }
+        public double[] a { get { return _innerobj.a; } set { _innerobj.a = value; } }
+    
+        public xdebugrecord1()
+        {
+            _innerobj = new xdebug.xdebugrecord1();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new xdebugrecord1((xdebug.xdebugrecord1)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private xdebug.xdebugrecord1 _innerobj;
+        public xdebug.xdebugrecord1 innerobj { get { return _innerobj; } }
+        public xdebugrecord1(xdebug.xdebugrecord1 obj)
+        {
+            _innerobj = obj;
+        }
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Creates and returns XDebugRecord1 structure:
+    * integer and complex fields of Rec1 are set to 1 and 1+i correspondingly
+    * array field of Rec1 is set to [2,3]
+
+      -- ALGLIB --
+         Copyright 27.05.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebuginitrecord1(out xdebugrecord1 rec1)
+    {
+        rec1 = new xdebugrecord1();
+        xdebug.xdebuginitrecord1(rec1.innerobj, null);
+    }
+    
+    public static void xdebuginitrecord1(out xdebugrecord1 rec1, alglib.xparams _params)
+    {
+        rec1 = new xdebugrecord1();
+        xdebug.xdebuginitrecord1(rec1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Counts number of True values in the boolean 1D array.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static int xdebugb1count(bool[] a)
+    {
+    
+        return xdebug.xdebugb1count(a, null);
+    }
+    
+    public static int xdebugb1count(bool[] a, alglib.xparams _params)
+    {
+    
+        return xdebug.xdebugb1count(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Replace all values in array by NOT(a[i]).
+    Array is passed using "shared" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugb1not(ref bool[] a)
+    {
+    
+        xdebug.xdebugb1not(a, null);
+    }
+    
+    public static void xdebugb1not(ref bool[] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugb1not(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Appends copy of array to itself.
+    Array is passed using "var" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugb1appendcopy(ref bool[] a)
+    {
+    
+        xdebug.xdebugb1appendcopy(ref a, null);
+    }
+    
+    public static void xdebugb1appendcopy(ref bool[] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugb1appendcopy(ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Generate N-element array with even-numbered elements set to True.
+    Array is passed using "out" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugb1outeven(int n, out bool[] a)
+    {
+        a = new bool[0];
+        xdebug.xdebugb1outeven(n, ref a, null);
+    }
+    
+    public static void xdebugb1outeven(int n, out bool[] a, alglib.xparams _params)
+    {
+        a = new bool[0];
+        xdebug.xdebugb1outeven(n, ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Returns sum of elements in the array.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static int xdebugi1sum(int[] a)
+    {
+    
+        return xdebug.xdebugi1sum(a, null);
+    }
+    
+    public static int xdebugi1sum(int[] a, alglib.xparams _params)
+    {
+    
+        return xdebug.xdebugi1sum(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Replace all values in array by -A[I]
+    Array is passed using "shared" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugi1neg(ref int[] a)
+    {
+    
+        xdebug.xdebugi1neg(a, null);
+    }
+    
+    public static void xdebugi1neg(ref int[] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugi1neg(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Appends copy of array to itself.
+    Array is passed using "var" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugi1appendcopy(ref int[] a)
+    {
+    
+        xdebug.xdebugi1appendcopy(ref a, null);
+    }
+    
+    public static void xdebugi1appendcopy(ref int[] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugi1appendcopy(ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Generate N-element array with even-numbered A[I] set to I, and odd-numbered
+    ones set to 0.
+
+    Array is passed using "out" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugi1outeven(int n, out int[] a)
+    {
+        a = new int[0];
+        xdebug.xdebugi1outeven(n, ref a, null);
+    }
+    
+    public static void xdebugi1outeven(int n, out int[] a, alglib.xparams _params)
+    {
+        a = new int[0];
+        xdebug.xdebugi1outeven(n, ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Returns sum of elements in the array.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static double xdebugr1sum(double[] a)
+    {
+    
+        return xdebug.xdebugr1sum(a, null);
+    }
+    
+    public static double xdebugr1sum(double[] a, alglib.xparams _params)
+    {
+    
+        return xdebug.xdebugr1sum(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Replace all values in array by -A[I]
+    Array is passed using "shared" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugr1neg(ref double[] a)
+    {
+    
+        xdebug.xdebugr1neg(a, null);
+    }
+    
+    public static void xdebugr1neg(ref double[] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugr1neg(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Appends copy of array to itself.
+    Array is passed using "var" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugr1appendcopy(ref double[] a)
+    {
+    
+        xdebug.xdebugr1appendcopy(ref a, null);
+    }
+    
+    public static void xdebugr1appendcopy(ref double[] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugr1appendcopy(ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Generate N-element array with even-numbered A[I] set to I*0.25,
+    and odd-numbered ones are set to 0.
+
+    Array is passed using "out" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugr1outeven(int n, out double[] a)
+    {
+        a = new double[0];
+        xdebug.xdebugr1outeven(n, ref a, null);
+    }
+    
+    public static void xdebugr1outeven(int n, out double[] a, alglib.xparams _params)
+    {
+        a = new double[0];
+        xdebug.xdebugr1outeven(n, ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Returns sum of elements in the array.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static complex xdebugc1sum(complex[] a)
+    {
+    
+        return xdebug.xdebugc1sum(a, null);
+    }
+    
+    public static complex xdebugc1sum(complex[] a, alglib.xparams _params)
+    {
+    
+        return xdebug.xdebugc1sum(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Replace all values in array by -A[I]
+    Array is passed using "shared" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugc1neg(ref complex[] a)
+    {
+    
+        xdebug.xdebugc1neg(a, null);
+    }
+    
+    public static void xdebugc1neg(ref complex[] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugc1neg(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Appends copy of array to itself.
+    Array is passed using "var" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugc1appendcopy(ref complex[] a)
+    {
+    
+        xdebug.xdebugc1appendcopy(ref a, null);
+    }
+    
+    public static void xdebugc1appendcopy(ref complex[] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugc1appendcopy(ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Generate N-element array with even-numbered A[K] set to (x,y) = (K*0.25, K*0.125)
+    and odd-numbered ones are set to 0.
+
+    Array is passed using "out" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugc1outeven(int n, out complex[] a)
+    {
+        a = new complex[0];
+        xdebug.xdebugc1outeven(n, ref a, null);
+    }
+    
+    public static void xdebugc1outeven(int n, out complex[] a, alglib.xparams _params)
+    {
+        a = new complex[0];
+        xdebug.xdebugc1outeven(n, ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Counts number of True values in the boolean 2D array.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static int xdebugb2count(bool[,] a)
+    {
+    
+        return xdebug.xdebugb2count(a, null);
+    }
+    
+    public static int xdebugb2count(bool[,] a, alglib.xparams _params)
+    {
+    
+        return xdebug.xdebugb2count(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Replace all values in array by NOT(a[i]).
+    Array is passed using "shared" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugb2not(ref bool[,] a)
+    {
+    
+        xdebug.xdebugb2not(a, null);
+    }
+    
+    public static void xdebugb2not(ref bool[,] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugb2not(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Transposes array.
+    Array is passed using "var" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugb2transpose(ref bool[,] a)
+    {
+    
+        xdebug.xdebugb2transpose(ref a, null);
+    }
+    
+    public static void xdebugb2transpose(ref bool[,] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugb2transpose(ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Generate MxN matrix with elements set to "Sin(3*I+5*J)>0"
+    Array is passed using "out" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugb2outsin(int m, int n, out bool[,] a)
+    {
+        a = new bool[0,0];
+        xdebug.xdebugb2outsin(m, n, ref a, null);
+    }
+    
+    public static void xdebugb2outsin(int m, int n, out bool[,] a, alglib.xparams _params)
+    {
+        a = new bool[0,0];
+        xdebug.xdebugb2outsin(m, n, ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Returns sum of elements in the array.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static int xdebugi2sum(int[,] a)
+    {
+    
+        return xdebug.xdebugi2sum(a, null);
+    }
+    
+    public static int xdebugi2sum(int[,] a, alglib.xparams _params)
+    {
+    
+        return xdebug.xdebugi2sum(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Replace all values in array by -a[i,j]
+    Array is passed using "shared" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugi2neg(ref int[,] a)
+    {
+    
+        xdebug.xdebugi2neg(a, null);
+    }
+    
+    public static void xdebugi2neg(ref int[,] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugi2neg(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Transposes array.
+    Array is passed using "var" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugi2transpose(ref int[,] a)
+    {
+    
+        xdebug.xdebugi2transpose(ref a, null);
+    }
+    
+    public static void xdebugi2transpose(ref int[,] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugi2transpose(ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Generate MxN matrix with elements set to "Sign(Sin(3*I+5*J))"
+    Array is passed using "out" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugi2outsin(int m, int n, out int[,] a)
+    {
+        a = new int[0,0];
+        xdebug.xdebugi2outsin(m, n, ref a, null);
+    }
+    
+    public static void xdebugi2outsin(int m, int n, out int[,] a, alglib.xparams _params)
+    {
+        a = new int[0,0];
+        xdebug.xdebugi2outsin(m, n, ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Returns sum of elements in the array.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static double xdebugr2sum(double[,] a)
+    {
+    
+        return xdebug.xdebugr2sum(a, null);
+    }
+    
+    public static double xdebugr2sum(double[,] a, alglib.xparams _params)
+    {
+    
+        return xdebug.xdebugr2sum(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Replace all values in array by -a[i,j]
+    Array is passed using "shared" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugr2neg(ref double[,] a)
+    {
+    
+        xdebug.xdebugr2neg(a, null);
+    }
+    
+    public static void xdebugr2neg(ref double[,] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugr2neg(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Transposes array.
+    Array is passed using "var" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugr2transpose(ref double[,] a)
+    {
+    
+        xdebug.xdebugr2transpose(ref a, null);
+    }
+    
+    public static void xdebugr2transpose(ref double[,] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugr2transpose(ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Generate MxN matrix with elements set to "Sin(3*I+5*J)"
+    Array is passed using "out" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugr2outsin(int m, int n, out double[,] a)
+    {
+        a = new double[0,0];
+        xdebug.xdebugr2outsin(m, n, ref a, null);
+    }
+    
+    public static void xdebugr2outsin(int m, int n, out double[,] a, alglib.xparams _params)
+    {
+        a = new double[0,0];
+        xdebug.xdebugr2outsin(m, n, ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Returns sum of elements in the array.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static complex xdebugc2sum(complex[,] a)
+    {
+    
+        return xdebug.xdebugc2sum(a, null);
+    }
+    
+    public static complex xdebugc2sum(complex[,] a, alglib.xparams _params)
+    {
+    
+        return xdebug.xdebugc2sum(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Replace all values in array by -a[i,j]
+    Array is passed using "shared" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugc2neg(ref complex[,] a)
+    {
+    
+        xdebug.xdebugc2neg(a, null);
+    }
+    
+    public static void xdebugc2neg(ref complex[,] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugc2neg(a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Transposes array.
+    Array is passed using "var" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugc2transpose(ref complex[,] a)
+    {
+    
+        xdebug.xdebugc2transpose(ref a, null);
+    }
+    
+    public static void xdebugc2transpose(ref complex[,] a, alglib.xparams _params)
+    {
+    
+        xdebug.xdebugc2transpose(ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Generate MxN matrix with elements set to "Sin(3*I+5*J),Cos(3*I+5*J)"
+    Array is passed using "out" convention.
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void xdebugc2outsincos(int m, int n, out complex[,] a)
+    {
+        a = new complex[0,0];
+        xdebug.xdebugc2outsincos(m, n, ref a, null);
+    }
+    
+    public static void xdebugc2outsincos(int m, int n, out complex[,] a, alglib.xparams _params)
+    {
+        a = new complex[0,0];
+        xdebug.xdebugc2outsincos(m, n, ref a, _params);
+    }
+    
+    /*************************************************************************
+    This is debug function intended for testing ALGLIB interface generator.
+    Never use it in any real life project.
+
+    Returns sum of a[i,j]*(1+b[i,j]) such that c[i,j] is True
+
+      -- ALGLIB --
+         Copyright 11.10.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static double xdebugmaskedbiasedproductsum(int m, int n, double[,] a, double[,] b, bool[,] c)
+    {
+    
+        return xdebug.xdebugmaskedbiasedproductsum(m, n, a, b, c, null);
+    }
+    
+    public static double xdebugmaskedbiasedproductsum(int m, int n, double[,] a, double[,] b, bool[,] c, alglib.xparams _params)
+    {
+    
+        return xdebug.xdebugmaskedbiasedproductsum(m, n, a, b, c, _params);
+    }
+
+}
+public partial class alglib
+{
+    public class nearestneighbor
+    {
+        /*************************************************************************
+        Buffer object which is used to perform nearest neighbor  requests  in  the
+        multithreaded mode (multiple threads working with same KD-tree object).
+
+        This object should be created with KDTreeCreateRequestBuffer().
+        *************************************************************************/
+        public class kdtreerequestbuffer : apobject
+        {
+            public double[] x;
+            public double[] boxmin;
+            public double[] boxmax;
+            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 kdtreerequestbuffer()
+            {
+                init();
+            }
+            public override void init()
+            {
+                x = new double[0];
+                boxmin = new double[0];
+                boxmax = new double[0];
+                idx = new int[0];
+                r = new double[0];
+                buf = new double[0];
+                curboxmin = new double[0];
+                curboxmax = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                kdtreerequestbuffer _result = new kdtreerequestbuffer();
+                _result.x = (double[])x.Clone();
+                _result.boxmin = (double[])boxmin.Clone();
+                _result.boxmax = (double[])boxmax.Clone();
+                _result.kneeded = kneeded;
+                _result.rneeded = rneeded;
+                _result.selfmatch = selfmatch;
+                _result.approxf = approxf;
+                _result.kcur = kcur;
+                _result.idx = (int[])idx.Clone();
+                _result.r = (double[])r.Clone();
+                _result.buf = (double[])buf.Clone();
+                _result.curboxmin = (double[])curboxmin.Clone();
+                _result.curboxmax = (double[])curboxmax.Clone();
+                _result.curdist = curdist;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        KD-tree object.
+        *************************************************************************/
+        public class kdtree : apobject
+        {
+            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 kdtreerequestbuffer innerbuf;
+            public int debugcounter;
+            public kdtree()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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];
+                innerbuf = new kdtreerequestbuffer();
+            }
+            public override alglib.apobject make_copy()
+            {
+                kdtree _result = new kdtree();
+                _result.n = n;
+                _result.nx = nx;
+                _result.ny = ny;
+                _result.normtype = normtype;
+                _result.xy = (double[,])xy.Clone();
+                _result.tags = (int[])tags.Clone();
+                _result.boxmin = (double[])boxmin.Clone();
+                _result.boxmax = (double[])boxmax.Clone();
+                _result.nodes = (int[])nodes.Clone();
+                _result.splits = (double[])splits.Clone();
+                _result.innerbuf = (kdtreerequestbuffer)innerbuf.make_copy();
+                _result.debugcounter = debugcounter;
+                return _result;
+            }
+        };
+
+
+
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "KDTreeBuild: XY 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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 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, _params), "KDTreeBuildTagged: XY contains infinite or NaN values");
+            
+            //
+            // initialize
+            //
+            kdt.n = n;
+            kdt.nx = nx;
+            kdt.ny = ny;
+            kdt.normtype = normtype;
+            kdt.innerbuf.kcur = 0;
+            
+            //
+            // N=0 => quick exit
+            //
+            if( n==0 )
+            {
+                return;
+            }
+            
+            //
+            // Allocate
+            //
+            kdtreeallocdatasetindependent(kdt, nx, ny, _params);
+            kdtreeallocdatasetdependent(kdt, n, nx, ny, _params);
+            kdtreecreaterequestbuffer(kdt, kdt.innerbuf, _params);
+            
+            //
+            // 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]);
+                }
+            }
+            
+            //
+            // Generate tree
+            //
+            nodesoffs = 0;
+            splitsoffs = 0;
+            for(i_=0; i_<=nx-1;i_++)
+            {
+                kdt.innerbuf.curboxmin[i_] = kdt.boxmin[i_];
+            }
+            for(i_=0; i_<=nx-1;i_++)
+            {
+                kdt.innerbuf.curboxmax[i_] = kdt.boxmax[i_];
+            }
+            kdtreegeneratetreerec(kdt, ref nodesoffs, ref splitsoffs, 0, n, 8, _params);
+            apserv.ivectorresize(ref kdt.nodes, nodesoffs, _params);
+            apserv.rvectorresize(ref kdt.splits, splitsoffs, _params);
+        }
+
+
+        /*************************************************************************
+        This function creates buffer  structure  which  can  be  used  to  perform
+        parallel KD-tree requests.
+
+        KD-tree subpackage provides two sets of request functions - ones which use
+        internal buffer of KD-tree object  (these  functions  are  single-threaded
+        because they use same buffer, which can not shared between  threads),  and
+        ones which use external buffer.
+
+        This function is used to initialize external buffer.
+
+        INPUT PARAMETERS
+            KDT         -   KD-tree which is associated with newly created buffer
+
+        OUTPUT PARAMETERS
+            Buf         -   external buffer.
+            
+            
+        IMPORTANT: KD-tree buffer should be used only with  KD-tree  object  which
+                   was used to initialize buffer. Any attempt to use buffer   with
+                   different object is dangerous - you  may  get  integrity  check
+                   failure (exception) because sizes of internal arrays do not fit
+                   to dimensions of KD-tree structure.
+
+          -- ALGLIB --
+             Copyright 18.03.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreecreaterequestbuffer(kdtree kdt,
+            kdtreerequestbuffer buf,
+            alglib.xparams _params)
+        {
+            buf.x = new double[kdt.nx];
+            buf.boxmin = new double[kdt.nx];
+            buf.boxmax = new double[kdt.nx];
+            buf.idx = new int[kdt.n];
+            buf.r = new double[kdt.n];
+            buf.buf = new double[Math.Max(kdt.n, kdt.nx)];
+            buf.curboxmin = new double[kdt.nx];
+            buf.curboxmax = new double[kdt.nx];
+            buf.kcur = 0;
+        }
+
+
+        /*************************************************************************
+        K-NN query: K nearest neighbors
+
+        IMPORTANT: this function can not be used in multithreaded code because  it
+                   uses internal temporary buffer of kd-tree object, which can not
+                   be shared between multiple threads.  If  you  want  to  perform
+                   parallel requests, use function  which  uses  external  request
+                   buffer: KDTreeTsQueryKNN() ("Ts" stands for "thread-safe").
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "KDTreeQueryKNN: X contains infinite or NaN values!");
+            result = kdtreetsqueryaknn(kdt, kdt.innerbuf, x, k, selfmatch, 0.0, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        K-NN query: K nearest neighbors, using external thread-local buffer.
+
+        You can call this function from multiple threads for same kd-tree instance,
+        assuming that different instances of buffer object are passed to different
+        threads.
+
+        INPUT PARAMETERS
+            KDT         -   kd-tree
+            Buf         -   request buffer  object  created  for  this  particular
+                            instance of kd-tree structure with kdtreecreaterequestbuffer()
+                            function.
+            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  buffer object. You can use following  subroutines  to
+        obtain these results (pay attention to "buf" in their names):
+        * KDTreeTsQueryResultsX() to get X-values
+        * KDTreeTsQueryResultsXY() to get X- and Y-values
+        * KDTreeTsQueryResultsTags() to get tag values
+        * KDTreeTsQueryResultsDistances() to get distances
+            
+        IMPORTANT: kd-tree buffer should be used only with  KD-tree  object  which
+                   was used to initialize buffer. Any attempt to use biffer   with
+                   different object is dangerous - you  may  get  integrity  check
+                   failure (exception) because sizes of internal arrays do not fit
+                   to dimensions of KD-tree structure.
+
+          -- ALGLIB --
+             Copyright 18.03.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static int kdtreetsqueryknn(kdtree kdt,
+            kdtreerequestbuffer buf,
+            double[] x,
+            int k,
+            bool selfmatch,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            alglib.ap.assert(k>=1, "KDTreeTsQueryKNN: K<1!");
+            alglib.ap.assert(alglib.ap.len(x)>=kdt.nx, "KDTreeTsQueryKNN: Length(X)<NX!");
+            alglib.ap.assert(apserv.isfinitevector(x, kdt.nx, _params), "KDTreeTsQueryKNN: X contains infinite or NaN values!");
+            result = kdtreetsqueryaknn(kdt, buf, x, k, selfmatch, 0.0, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        R-NN query: all points within R-sphere centered at X, ordered by  distance
+        between point and X (by ascending).
+
+        NOTE: it is also possible to perform undordered queries performed by means
+              of kdtreequeryrnnu() and kdtreetsqueryrnnu() functions. Such queries
+              are faster because we do not have to use heap structure for sorting.
+
+        IMPORTANT: this function can not be used in multithreaded code because  it
+                   uses internal temporary buffer of kd-tree object, which can not
+                   be shared between multiple threads.  If  you  want  to  perform
+                   parallel requests, use function  which  uses  external  request
+                   buffer: kdtreetsqueryrnn() ("Ts" stands for "thread-safe").
+
+        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,
+            alglib.xparams _params)
+        {
+            int result = 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, _params), "KDTreeQueryRNN: X contains infinite or NaN values!");
+            result = kdtreetsqueryrnn(kdt, kdt.innerbuf, x, r, selfmatch, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        R-NN query: all points within R-sphere  centered  at  X,  no  ordering  by
+        distance as undicated by "U" suffix (faster that ordered query, for  large
+        queries - significantly faster).
+
+        IMPORTANT: this function can not be used in multithreaded code because  it
+                   uses internal temporary buffer of kd-tree object, which can not
+                   be shared between multiple threads.  If  you  want  to  perform
+                   parallel requests, use function  which  uses  external  request
+                   buffer: kdtreetsqueryrnn() ("Ts" stands for "thread-safe").
+
+        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
+
+        As indicated by "U" suffix, this function returns unordered results.
+
+          -- ALGLIB --
+             Copyright 01.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static int kdtreequeryrnnu(kdtree kdt,
+            double[] x,
+            double r,
+            bool selfmatch,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            alglib.ap.assert((double)(r)>(double)(0), "KDTreeQueryRNNU: incorrect R!");
+            alglib.ap.assert(alglib.ap.len(x)>=kdt.nx, "KDTreeQueryRNNU: Length(X)<NX!");
+            alglib.ap.assert(apserv.isfinitevector(x, kdt.nx, _params), "KDTreeQueryRNNU: X contains infinite or NaN values!");
+            result = kdtreetsqueryrnnu(kdt, kdt.innerbuf, x, r, selfmatch, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        R-NN query: all points within  R-sphere  centered  at  X,  using  external
+        thread-local buffer, sorted by distance between point and X (by ascending)
+
+        You can call this function from multiple threads for same kd-tree instance,
+        assuming that different instances of buffer object are passed to different
+        threads.
+
+        NOTE: it is also possible to perform undordered queries performed by means
+              of kdtreequeryrnnu() and kdtreetsqueryrnnu() functions. Such queries
+              are faster because we do not have to use heap structure for sorting.
+
+        INPUT PARAMETERS
+            KDT         -   KD-tree
+            Buf         -   request buffer  object  created  for  this  particular
+                            instance of kd-tree structure with kdtreecreaterequestbuffer()
+                            function.
+            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  buffer object. You can use following  subroutines  to
+        obtain these results (pay attention to "buf" in their names):
+        * KDTreeTsQueryResultsX() to get X-values
+        * KDTreeTsQueryResultsXY() to get X- and Y-values
+        * KDTreeTsQueryResultsTags() to get tag values
+        * KDTreeTsQueryResultsDistances() to get distances
+            
+        IMPORTANT: kd-tree buffer should be used only with  KD-tree  object  which
+                   was used to initialize buffer. Any attempt to use biffer   with
+                   different object is dangerous - you  may  get  integrity  check
+                   failure (exception) because sizes of internal arrays do not fit
+                   to dimensions of KD-tree structure.
+
+          -- ALGLIB --
+             Copyright 18.03.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static int kdtreetsqueryrnn(kdtree kdt,
+            kdtreerequestbuffer buf,
+            double[] x,
+            double r,
+            bool selfmatch,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            alglib.ap.assert(math.isfinite(r) && (double)(r)>(double)(0), "KDTreeTsQueryRNN: incorrect R!");
+            alglib.ap.assert(alglib.ap.len(x)>=kdt.nx, "KDTreeTsQueryRNN: Length(X)<NX!");
+            alglib.ap.assert(apserv.isfinitevector(x, kdt.nx, _params), "KDTreeTsQueryRNN: X contains infinite or NaN values!");
+            result = tsqueryrnn(kdt, buf, x, r, selfmatch, true, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        R-NN query: all points within  R-sphere  centered  at  X,  using  external
+        thread-local buffer, no ordering by distance as undicated  by  "U"  suffix
+        (faster that ordered query, for large queries - significantly faster).
+
+        You can call this function from multiple threads for same kd-tree instance,
+        assuming that different instances of buffer object are passed to different
+        threads.
+
+        INPUT PARAMETERS
+            KDT         -   KD-tree
+            Buf         -   request buffer  object  created  for  this  particular
+                            instance of kd-tree structure with kdtreecreaterequestbuffer()
+                            function.
+            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  buffer object. You can use following  subroutines  to
+        obtain these results (pay attention to "buf" in their names):
+        * KDTreeTsQueryResultsX() to get X-values
+        * KDTreeTsQueryResultsXY() to get X- and Y-values
+        * KDTreeTsQueryResultsTags() to get tag values
+        * KDTreeTsQueryResultsDistances() to get distances
+
+        As indicated by "U" suffix, this function returns unordered results.
+            
+        IMPORTANT: kd-tree buffer should be used only with  KD-tree  object  which
+                   was used to initialize buffer. Any attempt to use biffer   with
+                   different object is dangerous - you  may  get  integrity  check
+                   failure (exception) because sizes of internal arrays do not fit
+                   to dimensions of KD-tree structure.
+
+          -- ALGLIB --
+             Copyright 18.03.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static int kdtreetsqueryrnnu(kdtree kdt,
+            kdtreerequestbuffer buf,
+            double[] x,
+            double r,
+            bool selfmatch,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            alglib.ap.assert(math.isfinite(r) && (double)(r)>(double)(0), "KDTreeTsQueryRNNU: incorrect R!");
+            alglib.ap.assert(alglib.ap.len(x)>=kdt.nx, "KDTreeTsQueryRNNU: Length(X)<NX!");
+            alglib.ap.assert(apserv.isfinitevector(x, kdt.nx, _params), "KDTreeTsQueryRNNU: X contains infinite or NaN values!");
+            result = tsqueryrnn(kdt, buf, x, r, selfmatch, false, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        K-NN query: approximate K nearest neighbors
+
+        IMPORTANT: this function can not be used in multithreaded code because  it
+                   uses internal temporary buffer of kd-tree object, which can not
+                   be shared between multiple threads.  If  you  want  to  perform
+                   parallel requests, use function  which  uses  external  request
+                   buffer: KDTreeTsQueryAKNN() ("Ts" stands for "thread-safe").
+
+        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,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = kdtreetsqueryaknn(kdt, kdt.innerbuf, x, k, selfmatch, eps, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        K-NN query: approximate K nearest neighbors, using thread-local buffer.
+
+        You can call this function from multiple threads for same kd-tree instance,
+        assuming that different instances of buffer object are passed to different
+        threads.
+
+        INPUT PARAMETERS
+            KDT         -   KD-tree
+            Buf         -   request buffer  object  created  for  this  particular
+                            instance of kd-tree structure with kdtreecreaterequestbuffer()
+                            function.
+            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  buffer object. You can use following  subroutines  to
+        obtain these results (pay attention to "buf" in their names):
+        * KDTreeTsQueryResultsX() to get X-values
+        * KDTreeTsQueryResultsXY() to get X- and Y-values
+        * KDTreeTsQueryResultsTags() to get tag values
+        * KDTreeTsQueryResultsDistances() to get distances
+            
+        IMPORTANT: kd-tree buffer should be used only with  KD-tree  object  which
+                   was used to initialize buffer. Any attempt to use biffer   with
+                   different object is dangerous - you  may  get  integrity  check
+                   failure (exception) because sizes of internal arrays do not fit
+                   to dimensions of KD-tree structure.
+
+          -- ALGLIB --
+             Copyright 18.03.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static int kdtreetsqueryaknn(kdtree kdt,
+            kdtreerequestbuffer buf,
+            double[] x,
+            int k,
+            bool selfmatch,
+            double eps,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(k>0, "KDTreeTsQueryAKNN: incorrect K!");
+            alglib.ap.assert((double)(eps)>=(double)(0), "KDTreeTsQueryAKNN: incorrect Eps!");
+            alglib.ap.assert(alglib.ap.len(x)>=kdt.nx, "KDTreeTsQueryAKNN: Length(X)<NX!");
+            alglib.ap.assert(apserv.isfinitevector(x, kdt.nx, _params), "KDTreeTsQueryAKNN: X contains infinite or NaN values!");
+            
+            //
+            // Handle special case: KDT.N=0
+            //
+            if( kdt.n==0 )
+            {
+                buf.kcur = 0;
+                result = 0;
+                return result;
+            }
+            
+            //
+            // Check consistency of request buffer
+            //
+            checkrequestbufferconsistency(kdt, buf, _params);
+            
+            //
+            // Prepare parameters
+            //
+            k = Math.Min(k, kdt.n);
+            buf.kneeded = k;
+            buf.rneeded = 0;
+            buf.selfmatch = selfmatch;
+            if( kdt.normtype==2 )
+            {
+                buf.approxf = 1/math.sqr(1+eps);
+            }
+            else
+            {
+                buf.approxf = 1/(1+eps);
+            }
+            buf.kcur = 0;
+            
+            //
+            // calculate distance from point to current bounding box
+            //
+            kdtreeinitbox(kdt, x, buf, _params);
+            
+            //
+            // call recursive search
+            // results are returned as heap
+            //
+            kdtreequerynnrec(kdt, buf, 0, _params);
+            
+            //
+            // pop from heap to generate ordered representation
+            //
+            // last element is non pop'ed because it is already in
+            // its place
+            //
+            result = buf.kcur;
+            j = buf.kcur;
+            for(i=buf.kcur; i>=2; i--)
+            {
+                tsort.tagheappopi(ref buf.r, ref buf.idx, ref j, _params);
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Box query: all points within user-specified box.
+
+        IMPORTANT: this function can not be used in multithreaded code because  it
+                   uses internal temporary buffer of kd-tree object, which can not
+                   be shared between multiple threads.  If  you  want  to  perform
+                   parallel requests, use function  which  uses  external  request
+                   buffer: KDTreeTsQueryBox() ("Ts" stands for "thread-safe").
+
+        INPUT PARAMETERS
+            KDT         -   KD-tree
+            BoxMin      -   lower bounds, array[0..NX-1].
+            BoxMax      -   upper bounds, array[0..NX-1].
+
+
+        RESULT
+            number of actual neighbors found (in [0,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() returns zeros for this request
+            
+        NOTE: this particular query returns unordered results, because there is no
+              meaningful way of  ordering  points.  Furthermore,  no 'distance' is
+              associated with points - it is either INSIDE  or OUTSIDE (so request
+              for distances will return zeros).
+
+          -- ALGLIB --
+             Copyright 14.05.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static int kdtreequerybox(kdtree kdt,
+            double[] boxmin,
+            double[] boxmax,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = kdtreetsquerybox(kdt, kdt.innerbuf, boxmin, boxmax, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Box query: all points within user-specified box, using thread-local buffer.
+
+        You can call this function from multiple threads for same kd-tree instance,
+        assuming that different instances of buffer object are passed to different
+        threads.
+
+        INPUT PARAMETERS
+            KDT         -   KD-tree
+            Buf         -   request buffer  object  created  for  this  particular
+                            instance of kd-tree structure with kdtreecreaterequestbuffer()
+                            function.
+            BoxMin      -   lower bounds, array[0..NX-1].
+            BoxMax      -   upper bounds, array[0..NX-1].
+
+        RESULT
+            number of actual neighbors found (in [0,N]).
+
+        This  subroutine  performs  query  and  stores  its result in the internal
+        structures  of  the  buffer object. You can use following  subroutines  to
+        obtain these results (pay attention to "ts" in their names):
+        * KDTreeTsQueryResultsX() to get X-values
+        * KDTreeTsQueryResultsXY() to get X- and Y-values
+        * KDTreeTsQueryResultsTags() to get tag values
+        * KDTreeTsQueryResultsDistances() returns zeros for this query
+            
+        NOTE: this particular query returns unordered results, because there is no
+              meaningful way of  ordering  points.  Furthermore,  no 'distance' is
+              associated with points - it is either INSIDE  or OUTSIDE (so request
+              for distances will return zeros).
+            
+        IMPORTANT: kd-tree buffer should be used only with  KD-tree  object  which
+                   was used to initialize buffer. Any attempt to use biffer   with
+                   different object is dangerous - you  may  get  integrity  check
+                   failure (exception) because sizes of internal arrays do not fit
+                   to dimensions of KD-tree structure.
+
+          -- ALGLIB --
+             Copyright 14.05.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static int kdtreetsquerybox(kdtree kdt,
+            kdtreerequestbuffer buf,
+            double[] boxmin,
+            double[] boxmax,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int j = 0;
+
+            alglib.ap.assert(alglib.ap.len(boxmin)>=kdt.nx, "KDTreeTsQueryBox: Length(BoxMin)<NX!");
+            alglib.ap.assert(alglib.ap.len(boxmax)>=kdt.nx, "KDTreeTsQueryBox: Length(BoxMax)<NX!");
+            alglib.ap.assert(apserv.isfinitevector(boxmin, kdt.nx, _params), "KDTreeTsQueryBox: BoxMin contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(boxmax, kdt.nx, _params), "KDTreeTsQueryBox: BoxMax contains infinite or NaN values!");
+            
+            //
+            // Check consistency of request buffer
+            //
+            checkrequestbufferconsistency(kdt, buf, _params);
+            
+            //
+            // Quick exit for degenerate boxes
+            //
+            for(j=0; j<=kdt.nx-1; j++)
+            {
+                if( (double)(boxmin[j])>(double)(boxmax[j]) )
+                {
+                    buf.kcur = 0;
+                    result = 0;
+                    return result;
+                }
+            }
+            
+            //
+            // Prepare parameters
+            //
+            for(j=0; j<=kdt.nx-1; j++)
+            {
+                buf.boxmin[j] = boxmin[j];
+                buf.boxmax[j] = boxmax[j];
+                buf.curboxmin[j] = boxmin[j];
+                buf.curboxmax[j] = boxmax[j];
+            }
+            buf.kcur = 0;
+            
+            //
+            // call recursive search
+            //
+            kdtreequeryboxrec(kdt, buf, 0, _params);
+            result = buf.kcur;
+            return result;
+        }
+
+
+        /*************************************************************************
+        X-values from last query.
+
+        This function retuns results stored in  the  internal  buffer  of  kd-tree
+        object. If you performed buffered requests (ones which  use  instances  of
+        kdtreerequestbuffer class), you  should  call  buffered  version  of  this
+        function - kdtreetsqueryresultsx().
+
+        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,
+            alglib.xparams _params)
+        {
+            kdtreetsqueryresultsx(kdt, kdt.innerbuf, ref x, _params);
+        }
+
+
+        /*************************************************************************
+        X- and Y-values from last query
+
+        This function retuns results stored in  the  internal  buffer  of  kd-tree
+        object. If you performed buffered requests (ones which  use  instances  of
+        kdtreerequestbuffer class), you  should  call  buffered  version  of  this
+        function - kdtreetsqueryresultsxy().
+
+        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,
+            alglib.xparams _params)
+        {
+            kdtreetsqueryresultsxy(kdt, kdt.innerbuf, ref xy, _params);
+        }
+
+
+        /*************************************************************************
+        Tags from last query
+
+        This function retuns results stored in  the  internal  buffer  of  kd-tree
+        object. If you performed buffered requests (ones which  use  instances  of
+        kdtreerequestbuffer class), you  should  call  buffered  version  of  this
+        function - kdtreetsqueryresultstags().
+
+        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,
+            alglib.xparams _params)
+        {
+            kdtreetsqueryresultstags(kdt, kdt.innerbuf, ref tags, _params);
+        }
+
+
+        /*************************************************************************
+        Distances from last query
+
+        This function retuns results stored in  the  internal  buffer  of  kd-tree
+        object. If you performed buffered requests (ones which  use  instances  of
+        kdtreerequestbuffer class), you  should  call  buffered  version  of  this
+        function - kdtreetsqueryresultsdistances().
+
+        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,
+            alglib.xparams _params)
+        {
+            kdtreetsqueryresultsdistances(kdt, kdt.innerbuf, ref r, _params);
+        }
+
+
+        /*************************************************************************
+        X-values from last query associated with kdtreerequestbuffer object.
+
+        INPUT PARAMETERS
+            KDT     -   KD-tree
+            Buf     -   request  buffer  object  created   for   this   particular
+                        instance of kd-tree structure.
+            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 kdtreetsqueryresultsx(kdtree kdt,
+            kdtreerequestbuffer buf,
+            ref double[,] x,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( buf.kcur==0 )
+            {
+                return;
+            }
+            if( alglib.ap.rows(x)<buf.kcur || alglib.ap.cols(x)<kdt.nx )
+            {
+                x = new double[buf.kcur, kdt.nx];
+            }
+            k = buf.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[buf.idx[i],i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        X- and Y-values from last query associated with kdtreerequestbuffer object.
+
+        INPUT PARAMETERS
+            KDT     -   KD-tree
+            Buf     -   request  buffer  object  created   for   this   particular
+                        instance of kd-tree structure.
+            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 kdtreetsqueryresultsxy(kdtree kdt,
+            kdtreerequestbuffer buf,
+            ref double[,] xy,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( buf.kcur==0 )
+            {
+                return;
+            }
+            if( alglib.ap.rows(xy)<buf.kcur || alglib.ap.cols(xy)<kdt.nx+kdt.ny )
+            {
+                xy = new double[buf.kcur, kdt.nx+kdt.ny];
+            }
+            k = buf.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[buf.idx[i],i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Tags from last query associated with kdtreerequestbuffer object.
+
+        This function retuns results stored in  the  internal  buffer  of  kd-tree
+        object. If you performed buffered requests (ones which  use  instances  of
+        kdtreerequestbuffer class), you  should  call  buffered  version  of  this
+        function - KDTreeTsqueryresultstags().
+
+        INPUT PARAMETERS
+            KDT     -   KD-tree
+            Buf     -   request  buffer  object  created   for   this   particular
+                        instance of kd-tree structure.
+            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 kdtreetsqueryresultstags(kdtree kdt,
+            kdtreerequestbuffer buf,
+            ref int[] tags,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+
+            if( buf.kcur==0 )
+            {
+                return;
+            }
+            if( alglib.ap.len(tags)<buf.kcur )
+            {
+                tags = new int[buf.kcur];
+            }
+            k = buf.kcur;
+            for(i=0; i<=k-1; i++)
+            {
+                tags[i] = kdt.tags[buf.idx[i]];
+            }
+        }
+
+
+        /*************************************************************************
+        Distances from last query associated with kdtreerequestbuffer object.
+
+        This function retuns results stored in  the  internal  buffer  of  kd-tree
+        object. If you performed buffered requests (ones which  use  instances  of
+        kdtreerequestbuffer class), you  should  call  buffered  version  of  this
+        function - KDTreeTsqueryresultsdistances().
+
+        INPUT PARAMETERS
+            KDT     -   KD-tree
+            Buf     -   request  buffer  object  created   for   this   particular
+                        instance of kd-tree structure.
+            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 kdtreetsqueryresultsdistances(kdtree kdt,
+            kdtreerequestbuffer buf,
+            ref double[] r,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+
+            if( buf.kcur==0 )
+            {
+                return;
+            }
+            if( alglib.ap.len(r)<buf.kcur )
+            {
+                r = new double[buf.kcur];
+            }
+            k = buf.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(buf.r[i]);
+                }
+            }
+            if( kdt.normtype==1 )
+            {
+                for(i=0; i<=k-1; i++)
+                {
+                    r[i] = Math.Abs(buf.r[i]);
+                }
+            }
+            if( kdt.normtype==2 )
+            {
+                for(i=0; i<=k-1; i++)
+                {
+                    r[i] = Math.Sqrt(Math.Abs(buf.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,
+            alglib.xparams _params)
+        {
+            x = new double[0,0];
+
+            kdtreequeryresultsx(kdt, ref x, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            xy = new double[0,0];
+
+            kdtreequeryresultsxy(kdt, ref xy, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            tags = new int[0];
+
+            kdtreequeryresultstags(kdt, ref tags, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            r = new double[0];
+
+            kdtreequeryresultsdistances(kdt, ref r, _params);
+        }
+
+
+        /*************************************************************************
+        It is informational function which returns bounding box for entire dataset.
+        This function is not visible to ALGLIB users, only ALGLIB itself  may  use
+        it.
+
+        This function assumes that output buffers are preallocated by caller.
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreeexplorebox(kdtree kdt,
+            ref double[] boxmin,
+            ref double[] boxmax,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            apserv.rvectorsetlengthatleast(ref boxmin, kdt.nx, _params);
+            apserv.rvectorsetlengthatleast(ref boxmax, kdt.nx, _params);
+            for(i=0; i<=kdt.nx-1; i++)
+            {
+                boxmin[i] = kdt.boxmin[i];
+                boxmax[i] = kdt.boxmax[i];
+            }
+        }
+
+
+        /*************************************************************************
+        It is informational function which allows to get  information  about  node
+        type. Node index is given by integer value, with 0  corresponding  to root
+        node and other node indexes obtained via exploration.
+
+        You should not expect that serialization/unserialization will retain  node
+        indexes. You should keep in  mind  that  future  versions  of  ALGLIB  may
+        introduce new node types.
+
+        OUTPUT VALUES:
+            NodeType    -   node type:
+                            * 0 corresponds to leaf node, which can be explored by
+                              kdtreeexploreleaf() function
+                            * 1 corresponds to split node, which can  be  explored
+                              by kdtreeexploresplit() function
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreeexplorenodetype(kdtree kdt,
+            int node,
+            ref int nodetype,
+            alglib.xparams _params)
+        {
+            nodetype = 0;
+
+            alglib.ap.assert(node>=0, "KDTreeExploreNodeType: incorrect node");
+            alglib.ap.assert(node<alglib.ap.len(kdt.nodes), "KDTreeExploreNodeType: incorrect node");
+            if( kdt.nodes[node]>0 )
+            {
+                
+                //
+                // Leaf node
+                //
+                nodetype = 0;
+                return;
+            }
+            if( kdt.nodes[node]==0 )
+            {
+                
+                //
+                // Split node
+                //
+                nodetype = 1;
+                return;
+            }
+            alglib.ap.assert(false, "KDTreeExploreNodeType: integrity check failure");
+        }
+
+
+        /*************************************************************************
+        It is informational function which allows to get  information  about  leaf
+        node. Node index is given by integer value, with 0  corresponding  to root
+        node and other node indexes obtained via exploration.
+
+        You should not expect that serialization/unserialization will retain  node
+        indexes. You should keep in  mind  that  future  versions  of  ALGLIB  may
+        introduce new node types.
+
+        OUTPUT VALUES:
+            XT      -   output buffer is reallocated (if too small) and filled by
+                        XY values
+            K       -   number of rows in XY
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreeexploreleaf(kdtree kdt,
+            int node,
+            ref double[,] xy,
+            ref int k,
+            alglib.xparams _params)
+        {
+            int offs = 0;
+            int i = 0;
+            int j = 0;
+
+            k = 0;
+
+            alglib.ap.assert(node>=0, "KDTreeExploreLeaf: incorrect node index");
+            alglib.ap.assert(node+1<alglib.ap.len(kdt.nodes), "KDTreeExploreLeaf: incorrect node index");
+            alglib.ap.assert(kdt.nodes[node]>0, "KDTreeExploreLeaf: incorrect node index");
+            k = kdt.nodes[node];
+            offs = kdt.nodes[node+1];
+            alglib.ap.assert(offs>=0, "KDTreeExploreLeaf: integrity error");
+            alglib.ap.assert(offs+k-1<alglib.ap.rows(kdt.xy), "KDTreeExploreLeaf: integrity error");
+            apserv.rmatrixsetlengthatleast(ref xy, k, kdt.nx+kdt.ny, _params);
+            for(i=0; i<=k-1; i++)
+            {
+                for(j=0; j<=kdt.nx+kdt.ny-1; j++)
+                {
+                    xy[i,j] = kdt.xy[offs+i,kdt.nx+j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        It is informational function which allows to get  information  about split
+        node. Node index is given by integer value, with 0  corresponding  to root
+        node and other node indexes obtained via exploration.
+
+        You should not expect that serialization/unserialization will retain  node
+        indexes. You should keep in  mind  that  future  versions  of  ALGLIB  may
+        introduce new node types.
+
+        OUTPUT VALUES:
+            XT      -   output buffer is reallocated (if too small) and filled by
+                        XY values
+            K       -   number of rows in XY
+
+            //      Nodes[idx+1]=dim    dimension to split
+            //      Nodes[idx+2]=offs   offset of splitting point in Splits[]
+            //      Nodes[idx+3]=left   position of left child in Nodes[]
+            //      Nodes[idx+4]=right  position of right child in Nodes[]
+            
+          -- ALGLIB --
+             Copyright 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreeexploresplit(kdtree kdt,
+            int node,
+            ref int d,
+            ref double s,
+            ref int nodele,
+            ref int nodege,
+            alglib.xparams _params)
+        {
+            d = 0;
+            s = 0;
+            nodele = 0;
+            nodege = 0;
+
+            alglib.ap.assert(node>=0, "KDTreeExploreSplit: incorrect node index");
+            alglib.ap.assert(node+4<alglib.ap.len(kdt.nodes), "KDTreeExploreSplit: incorrect node index");
+            alglib.ap.assert(kdt.nodes[node]==0, "KDTreeExploreSplit: incorrect node index");
+            d = kdt.nodes[node+1];
+            s = kdt.splits[kdt.nodes[node+2]];
+            nodele = kdt.nodes[node+3];
+            nodege = kdt.nodes[node+4];
+            alglib.ap.assert(d>=0, "KDTreeExploreSplit: integrity failure");
+            alglib.ap.assert(d<kdt.nx, "KDTreeExploreSplit: integrity failure");
+            alglib.ap.assert(math.isfinite(s), "KDTreeExploreSplit: integrity failure");
+            alglib.ap.assert(nodele>=0, "KDTreeExploreSplit: integrity failure");
+            alglib.ap.assert(nodele<alglib.ap.len(kdt.nodes), "KDTreeExploreSplit: integrity failure");
+            alglib.ap.assert(nodege>=0, "KDTreeExploreSplit: integrity failure");
+            alglib.ap.assert(nodege<alglib.ap.len(kdt.nodes), "KDTreeExploreSplit: integrity failure");
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreealloc(alglib.serializer s,
+            kdtree tree,
+            alglib.xparams _params)
+        {
+            
+            //
+            // 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, _params);
+            apserv.allocintegerarray(s, tree.tags, -1, _params);
+            apserv.allocrealarray(s, tree.boxmin, -1, _params);
+            apserv.allocrealarray(s, tree.boxmax, -1, _params);
+            apserv.allocintegerarray(s, tree.nodes, -1, _params);
+            apserv.allocrealarray(s, tree.splits, -1, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreeserialize(alglib.serializer s,
+            kdtree tree,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Header
+            //
+            s.serialize_int(scodes.getkdtreeserializationcode(_params));
+            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, _params);
+            apserv.serializeintegerarray(s, tree.tags, -1, _params);
+            apserv.serializerealarray(s, tree.boxmin, -1, _params);
+            apserv.serializerealarray(s, tree.boxmax, -1, _params);
+            apserv.serializeintegerarray(s, tree.nodes, -1, _params);
+            apserv.serializerealarray(s, tree.splits, -1, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kdtreeunserialize(alglib.serializer s,
+            kdtree tree,
+            alglib.xparams _params)
+        {
+            int i0 = 0;
+            int i1 = 0;
+
+            
+            //
+            // check correctness of header
+            //
+            i0 = s.unserialize_int();
+            alglib.ap.assert(i0==scodes.getkdtreeserializationcode(_params), "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, _params);
+            apserv.unserializeintegerarray(s, ref tree.tags, _params);
+            apserv.unserializerealarray(s, ref tree.boxmin, _params);
+            apserv.unserializerealarray(s, ref tree.boxmax, _params);
+            apserv.unserializeintegerarray(s, ref tree.nodes, _params);
+            apserv.unserializerealarray(s, ref tree.splits, _params);
+            kdtreecreaterequestbuffer(tree, tree.innerbuf, _params);
+        }
+
+
+        /*************************************************************************
+        R-NN query: all points within  R-sphere  centered  at  X,  using  external
+        thread-local buffer, sorted by distance between point and X (by ascending)
+
+        You can call this function from multiple threads for same kd-tree instance,
+        assuming that different instances of buffer object are passed to different
+        threads.
+
+        NOTE: it is also possible to perform undordered queries performed by means
+              of kdtreequeryrnnu() and kdtreetsqueryrnnu() functions. Such queries
+              are faster because we do not have to use heap structure for sorting.
+
+        INPUT PARAMETERS
+            KDT         -   KD-tree
+            Buf         -   request buffer  object  created  for  this  particular
+                            instance of kd-tree structure with kdtreecreaterequestbuffer()
+                            function.
+            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  buffer object. You can use following  subroutines  to
+        obtain these results (pay attention to "buf" in their names):
+        * KDTreeTsQueryResultsX() to get X-values
+        * KDTreeTsQueryResultsXY() to get X- and Y-values
+        * KDTreeTsQueryResultsTags() to get tag values
+        * KDTreeTsQueryResultsDistances() to get distances
+            
+        IMPORTANT: kd-tree buffer should be used only with  KD-tree  object  which
+                   was used to initialize buffer. Any attempt to use biffer   with
+                   different object is dangerous - you  may  get  integrity  check
+                   failure (exception) because sizes of internal arrays do not fit
+                   to dimensions of KD-tree structure.
+
+          -- ALGLIB --
+             Copyright 18.03.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static int tsqueryrnn(kdtree kdt,
+            kdtreerequestbuffer buf,
+            double[] x,
+            double r,
+            bool selfmatch,
+            bool orderedbydist,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+            int j = 0;
+
+            
+            //
+            // Handle special case: KDT.N=0
+            //
+            if( kdt.n==0 )
+            {
+                buf.kcur = 0;
+                result = 0;
+                return result;
+            }
+            
+            //
+            // Check consistency of request buffer
+            //
+            checkrequestbufferconsistency(kdt, buf, _params);
+            
+            //
+            // Prepare parameters
+            //
+            buf.kneeded = 0;
+            if( kdt.normtype!=2 )
+            {
+                buf.rneeded = r;
+            }
+            else
+            {
+                buf.rneeded = math.sqr(r);
+            }
+            buf.selfmatch = selfmatch;
+            buf.approxf = 1;
+            buf.kcur = 0;
+            
+            //
+            // calculate distance from point to current bounding box
+            //
+            kdtreeinitbox(kdt, x, buf, _params);
+            
+            //
+            // call recursive search
+            // results are returned as heap
+            //
+            kdtreequerynnrec(kdt, buf, 0, _params);
+            result = buf.kcur;
+            
+            //
+            // pop from heap to generate ordered representation
+            //
+            // last element is not pop'ed because it is already in
+            // its place
+            //
+            if( orderedbydist )
+            {
+                j = buf.kcur;
+                for(i=buf.kcur; i>=2; i--)
+                {
+                    tsort.tagheappopi(ref buf.r, ref buf.idx, ref j, _params);
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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( kdt.xy[ileft,d]<=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( kdt.xy[ileft,d]<=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,
+            alglib.xparams _params)
+        {
+            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;
+            double v0 = 0;
+            double v1 = 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
+            // In case bounding box has zero size, we enforce creation of the leaf node.
+            //
+            d = 0;
+            ds = kdt.innerbuf.curboxmax[0]-kdt.innerbuf.curboxmin[0];
+            for(i=1; i<=nx-1; i++)
+            {
+                v = kdt.innerbuf.curboxmax[i]-kdt.innerbuf.curboxmin[i];
+                if( v>ds )
+                {
+                    ds = v;
+                    d = i;
+                }
+            }
+            if( (double)(ds)==(double)(0) )
+            {
+                kdt.nodes[nodesoffs+0] = i2-i1;
+                kdt.nodes[nodesoffs+1] = i1;
+                nodesoffs = nodesoffs+2;
+                return;
+            }
+            
+            //
+            // Select split position S using sliding midpoint rule,
+            // rearrange points into [I1,I3) and [I3,I2).
+            //
+            // In case all points has same value of D-th component
+            // (MinV=MaxV) we enforce D-th dimension of bounding
+            // box to become exactly zero and repeat tree construction.
+            //
+            s = kdt.innerbuf.curboxmin[d]+0.5*ds;
+            i1_ = (i1) - (0);
+            for(i_=0; i_<=i2-i1-1;i_++)
+            {
+                kdt.innerbuf.buf[i_] = kdt.xy[i_+i1_,d];
+            }
+            n = i2-i1;
+            cntless = 0;
+            cntgreater = 0;
+            minv = kdt.innerbuf.buf[0];
+            maxv = kdt.innerbuf.buf[0];
+            minidx = i1;
+            maxidx = i1;
+            for(i=0; i<=n-1; i++)
+            {
+                v = kdt.innerbuf.buf[i];
+                if( v<minv )
+                {
+                    minv = v;
+                    minidx = i1+i;
+                }
+                if( v>maxv )
+                {
+                    maxv = v;
+                    maxidx = i1+i;
+                }
+                if( v<s )
+                {
+                    cntless = cntless+1;
+                }
+                if( v>s )
+                {
+                    cntgreater = cntgreater+1;
+                }
+            }
+            if( minv==maxv )
+            {
+                
+                //
+                // In case all points has same value of D-th component
+                // (MinV=MaxV) we enforce D-th dimension of bounding
+                // box to become exactly zero and repeat tree construction.
+                //
+                v0 = kdt.innerbuf.curboxmin[d];
+                v1 = kdt.innerbuf.curboxmax[d];
+                kdt.innerbuf.curboxmin[d] = minv;
+                kdt.innerbuf.curboxmax[d] = maxv;
+                kdtreegeneratetreerec(kdt, ref nodesoffs, ref splitsoffs, i1, i2, maxleafsize, _params);
+                kdt.innerbuf.curboxmin[d] = v0;
+                kdt.innerbuf.curboxmax[d] = v1;
+                return;
+            }
+            if( cntless>0 && cntgreater>0 )
+            {
+                
+                //
+                // normal midpoint split
+                //
+                kdtreesplit(kdt, i1, i2, d, s, ref i3, _params);
+            }
+            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*nx+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*nx+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;
+            
+            //
+            // Recursive generation:
+            // * update CurBox
+            // * call subroutine
+            // * restore CurBox
+            //
+            kdt.nodes[oldoffs+3] = nodesoffs;
+            v = kdt.innerbuf.curboxmax[d];
+            kdt.innerbuf.curboxmax[d] = s;
+            kdtreegeneratetreerec(kdt, ref nodesoffs, ref splitsoffs, i1, i3, maxleafsize, _params);
+            kdt.innerbuf.curboxmax[d] = v;
+            kdt.nodes[oldoffs+4] = nodesoffs;
+            v = kdt.innerbuf.curboxmin[d];
+            kdt.innerbuf.curboxmin[d] = s;
+            kdtreegeneratetreerec(kdt, ref nodesoffs, ref splitsoffs, i3, i2, maxleafsize, _params);
+            kdt.innerbuf.curboxmin[d] = v;
+            
+            //
+            // Zero-fill unused portions of the node (avoid false warnings by Valgrind
+            // about attempt to serialize uninitialized values)
+            //
+            alglib.ap.assert(splitnodesize==6, "KDTreeGenerateTreeRec: node size has unexpectedly changed");
+            kdt.nodes[oldoffs+5] = 0;
+        }
+
+
+        /*************************************************************************
+        Recursive subroutine for NN queries.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void kdtreequerynnrec(kdtree kdt,
+            kdtreerequestbuffer buf,
+            int offs,
+            alglib.xparams _params)
+        {
+            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]-buf.x[j]));
+                        }
+                    }
+                    if( kdt.normtype==1 )
+                    {
+                        for(j=0; j<=nx-1; j++)
+                        {
+                            ptdist = ptdist+Math.Abs(kdt.xy[i,j]-buf.x[j]);
+                        }
+                    }
+                    if( kdt.normtype==2 )
+                    {
+                        for(j=0; j<=nx-1; j++)
+                        {
+                            ptdist = ptdist+math.sqr(kdt.xy[i,j]-buf.x[j]);
+                        }
+                    }
+                    
+                    //
+                    // Skip points with zero distance if self-matches are turned off
+                    //
+                    if( ptdist==0 && !buf.selfmatch )
+                    {
+                        continue;
+                    }
+                    
+                    //
+                    // We CAN'T process point if R-criterion isn't satisfied,
+                    // i.e. (RNeeded<>0) AND (PtDist>R).
+                    //
+                    if( buf.rneeded==0 || ptdist<=buf.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( buf.kcur<buf.kneeded || buf.kneeded==0 )
+                        {
+                            
+                            //
+                            // add current point to heap without replacement
+                            //
+                            tsort.tagheappushi(ref buf.r, ref buf.idx, ref buf.kcur, ptdist, i, _params);
+                        }
+                        else
+                        {
+                            
+                            //
+                            // New points are added or not, depending on their distance.
+                            // If added, they replace element at the top of the heap
+                            //
+                            if( ptdist<buf.r[0] )
+                            {
+                                if( buf.kneeded==1 )
+                                {
+                                    buf.idx[0] = i;
+                                    buf.r[0] = ptdist;
+                                }
+                                else
+                                {
+                                    tsort.tagheapreplacetopi(ref buf.r, ref buf.idx, buf.kneeded, ptdist, i, _params);
+                                }
+                            }
+                        }
+                    }
+                }
+                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( buf.x[d]<=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 = buf.curdist;
+                        t1 = buf.x[d];
+                        v = buf.curboxmin[d];
+                        if( t1<=s )
+                        {
+                            if( kdt.normtype==0 )
+                            {
+                                buf.curdist = Math.Max(buf.curdist, s-t1);
+                            }
+                            if( kdt.normtype==1 )
+                            {
+                                buf.curdist = buf.curdist-Math.Max(v-t1, 0)+s-t1;
+                            }
+                            if( kdt.normtype==2 )
+                            {
+                                buf.curdist = buf.curdist-math.sqr(Math.Max(v-t1, 0))+math.sqr(s-t1);
+                            }
+                        }
+                        buf.curboxmin[d] = s;
+                    }
+                    else
+                    {
+                        prevdist = buf.curdist;
+                        t1 = buf.x[d];
+                        v = buf.curboxmax[d];
+                        if( t1>=s )
+                        {
+                            if( kdt.normtype==0 )
+                            {
+                                buf.curdist = Math.Max(buf.curdist, t1-s);
+                            }
+                            if( kdt.normtype==1 )
+                            {
+                                buf.curdist = buf.curdist-Math.Max(t1-v, 0)+t1-s;
+                            }
+                            if( kdt.normtype==2 )
+                            {
+                                buf.curdist = buf.curdist-math.sqr(Math.Max(t1-v, 0))+math.sqr(t1-s);
+                            }
+                        }
+                        buf.curboxmax[d] = s;
+                    }
+                    
+                    //
+                    // Decide: to dive into cell or not to dive
+                    //
+                    if( buf.rneeded!=0 && buf.curdist>buf.rneeded )
+                    {
+                        todive = false;
+                    }
+                    else
+                    {
+                        if( buf.kcur<buf.kneeded || buf.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 = buf.curdist<=buf.r[0]*buf.approxf;
+                        }
+                    }
+                    if( todive )
+                    {
+                        kdtreequerynnrec(kdt, buf, childoffs, _params);
+                    }
+                    
+                    //
+                    // Restore bounding box and distance
+                    //
+                    if( updatemin )
+                    {
+                        buf.curboxmin[d] = v;
+                    }
+                    else
+                    {
+                        buf.curboxmax[d] = v;
+                    }
+                    buf.curdist = prevdist;
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        Recursive subroutine for box queries.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void kdtreequeryboxrec(kdtree kdt,
+            kdtreerequestbuffer buf,
+            int offs,
+            alglib.xparams _params)
+        {
+            bool inbox = new bool();
+            int nx = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int i = 0;
+            int j = 0;
+            int d = 0;
+            double s = 0;
+            double v = 0;
+
+            alglib.ap.assert(kdt.n>0, "KDTreeQueryBoxRec: internal error");
+            nx = kdt.nx;
+            
+            //
+            // Check that intersection of query box with bounding box is non-empty.
+            // This check is performed once for Offs=0 (tree root).
+            //
+            if( offs==0 )
+            {
+                for(j=0; j<=nx-1; j++)
+                {
+                    if( buf.boxmin[j]>buf.curboxmax[j] )
+                    {
+                        return;
+                    }
+                    if( buf.boxmax[j]<buf.curboxmin[j] )
+                    {
+                        return;
+                    }
+                }
+            }
+            
+            //
+            // 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++)
+                {
+                    
+                    //
+                    // Check whether point is in box or not
+                    //
+                    inbox = true;
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        inbox = inbox && kdt.xy[i,j]>=buf.boxmin[j];
+                        inbox = inbox && kdt.xy[i,j]<=buf.boxmax[j];
+                    }
+                    if( !inbox )
+                    {
+                        continue;
+                    }
+                    
+                    //
+                    // Add point to unordered list
+                    //
+                    buf.r[buf.kcur] = 0.0;
+                    buf.idx[buf.kcur] = i;
+                    buf.kcur = buf.kcur+1;
+                }
+                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]];
+                
+                //
+                // Check lower split (S is upper bound of new bounding box)
+                //
+                if( s>=buf.boxmin[d] )
+                {
+                    v = buf.curboxmax[d];
+                    buf.curboxmax[d] = s;
+                    kdtreequeryboxrec(kdt, buf, kdt.nodes[offs+3], _params);
+                    buf.curboxmax[d] = v;
+                }
+                
+                //
+                // Check upper split (S is lower bound of new bounding box)
+                //
+                if( s<=buf.boxmax[d] )
+                {
+                    v = buf.curboxmin[d];
+                    buf.curboxmin[d] = s;
+                    kdtreequeryboxrec(kdt, buf, kdt.nodes[offs+4], _params);
+                    buf.curboxmin[d] = v;
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        Copies X[] to Buf.X[]
+        Loads distance from X[] to bounding box.
+        Initializes Buf.CurBox[].
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void kdtreeinitbox(kdtree kdt,
+            double[] x,
+            kdtreerequestbuffer buf,
+            alglib.xparams _params)
+        {
+            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
+            //
+            buf.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];
+                    buf.x[i] = vx;
+                    buf.curboxmin[i] = vmin;
+                    buf.curboxmax[i] = vmax;
+                    if( vx<vmin )
+                    {
+                        buf.curdist = Math.Max(buf.curdist, vmin-vx);
+                    }
+                    else
+                    {
+                        if( vx>vmax )
+                        {
+                            buf.curdist = Math.Max(buf.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];
+                    buf.x[i] = vx;
+                    buf.curboxmin[i] = vmin;
+                    buf.curboxmax[i] = vmax;
+                    if( vx<vmin )
+                    {
+                        buf.curdist = buf.curdist+vmin-vx;
+                    }
+                    else
+                    {
+                        if( vx>vmax )
+                        {
+                            buf.curdist = buf.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];
+                    buf.x[i] = vx;
+                    buf.curboxmin[i] = vmin;
+                    buf.curboxmax[i] = vmax;
+                    if( vx<vmin )
+                    {
+                        buf.curdist = buf.curdist+math.sqr(vmin-vx);
+                    }
+                    else
+                    {
+                        if( vx>vmax )
+                        {
+                            buf.curdist = buf.curdist+math.sqr(vx-vmax);
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function allocates all dataset-independend 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.xparams _params)
+        {
+            alglib.ap.assert(kdt.n>0, "KDTreeAllocDatasetIndependent: internal error");
+            kdt.boxmin = new double[nx];
+            kdt.boxmax = 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.xparams _params)
+        {
+            alglib.ap.assert(n>0, "KDTreeAllocDatasetDependent: internal error");
+            kdt.xy = new double[n, 2*nx+ny];
+            kdt.tags = new int[n];
+            kdt.nodes = new int[splitnodesize*2*n];
+            kdt.splits = new double[2*n];
+        }
+
+
+        /*************************************************************************
+        This  function   checks  consistency  of  request  buffer  structure  with
+        dimensions of kd-tree object.
+
+          -- ALGLIB --
+             Copyright 02.04.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static void checkrequestbufferconsistency(kdtree kdt,
+            kdtreerequestbuffer buf,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(alglib.ap.len(buf.x)>=kdt.nx, "KDTree: dimensions of kdtreerequestbuffer are inconsistent with kdtree structure");
+            alglib.ap.assert(alglib.ap.len(buf.idx)>=kdt.n, "KDTree: dimensions of kdtreerequestbuffer are inconsistent with kdtree structure");
+            alglib.ap.assert(alglib.ap.len(buf.r)>=kdt.n, "KDTree: dimensions of kdtreerequestbuffer are inconsistent with kdtree structure");
+            alglib.ap.assert(alglib.ap.len(buf.buf)>=Math.Max(kdt.n, kdt.nx), "KDTree: dimensions of kdtreerequestbuffer are inconsistent with kdtree structure");
+            alglib.ap.assert(alglib.ap.len(buf.curboxmin)>=kdt.nx, "KDTree: dimensions of kdtreerequestbuffer are inconsistent with kdtree structure");
+            alglib.ap.assert(alglib.ap.len(buf.curboxmax)>=kdt.nx, "KDTree: dimensions of kdtreerequestbuffer are inconsistent with kdtree structure");
+        }
+
+
+    }
+    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 : apobject
+        {
+            public int s1;
+            public int s2;
+            public int magicv;
+            public hqrndstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                hqrndstate _result = new hqrndstate();
+                _result.s1 = s1;
+                _result.s2 = s2;
+                _result.magicv = magicv;
+                return _result;
+            }
+        };
+
+
+
+
+        public const int hqrndmax = 2147483561;
+        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,
+            alglib.xparams _params)
+        {
+            int s0 = 0;
+            int s1 = 0;
+
+            s0 = math.randominteger(hqrndm1);
+            s1 = math.randominteger(hqrndm2);
+            hqrndseed(s0, s1, state, _params);
+        }
+
+
+        /*************************************************************************
+        HQRNDState initialization with seed values
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hqrndseed(int s1,
+            int s2,
+            hqrndstate state,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Protection against negative seeds:
+            //
+            //     SEED := -(SEED+1)
+            //
+            // We can use just "-SEED" because there exists such integer number  N
+            // that N<0, -N=N<0 too. (This number is equal to 0x800...000).   Need
+            // to handle such seed correctly forces us to use  a  bit  complicated
+            // formula.
+            //
+            if( s1<0 )
+            {
+                s1 = -(s1+1);
+            }
+            if( s2<0 )
+            {
+                s2 = -(s2+1);
+            }
+            state.s1 = s1%(hqrndm1-1)+1;
+            state.s2 = s2%(hqrndm2-1)+1;
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = (double)(hqrndintegerbase(state, _params)+1)/(double)(hqrndmax+2);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function generates random integer number in [0, N)
+
+        1. State structure must be initialized with HQRNDRandomize() or HQRNDSeed()
+        2. N can be any positive number except for very large numbers:
+           * close to 2^31 on 32-bit systems
+           * close to 2^62 on 64-bit systems
+           An exception will be generated if N is too large.
+
+          -- ALGLIB --
+             Copyright 02.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static int hqrnduniformi(hqrndstate state,
+            int n,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int maxcnt = 0;
+            int mx = 0;
+            int a = 0;
+            int b = 0;
+
+            alglib.ap.assert(n>0, "HQRNDUniformI: N<=0!");
+            maxcnt = hqrndmax+1;
+            
+            //
+            // Two branches: one for N<=MaxCnt, another for N>MaxCnt.
+            //
+            if( n>maxcnt )
+            {
+                
+                //
+                // N>=MaxCnt.
+                //
+                // We have two options here:
+                // a) N is exactly divisible by MaxCnt
+                // b) N is not divisible by MaxCnt
+                //
+                // In both cases we reduce problem on interval spanning [0,N)
+                // to several subproblems on intervals spanning [0,MaxCnt).
+                //
+                if( n%maxcnt==0 )
+                {
+                    
+                    //
+                    // N is exactly divisible by MaxCnt.
+                    //
+                    // [0,N) range is dividided into N/MaxCnt bins,
+                    // each of them having length equal to MaxCnt.
+                    //
+                    // We generate:
+                    // * random bin number B
+                    // * random offset within bin A
+                    // Both random numbers are generated by recursively
+                    // calling HQRNDUniformI().
+                    //
+                    // Result is equal to A+MaxCnt*B.
+                    //
+                    alglib.ap.assert(n/maxcnt<=maxcnt, "HQRNDUniformI: N is too large");
+                    a = hqrnduniformi(state, maxcnt, _params);
+                    b = hqrnduniformi(state, n/maxcnt, _params);
+                    result = a+maxcnt*b;
+                }
+                else
+                {
+                    
+                    //
+                    // N is NOT exactly divisible by MaxCnt.
+                    //
+                    // [0,N) range is dividided into Ceil(N/MaxCnt) bins,
+                    // each of them having length equal to MaxCnt.
+                    //
+                    // We generate:
+                    // * random bin number B in [0, Ceil(N/MaxCnt)-1]
+                    // * random offset within bin A
+                    // * if both of what is below is true
+                    //   1) bin number B is that of the last bin
+                    //   2) A >= N mod MaxCnt
+                    //   then we repeat generation of A/B.
+                    //   This stage is essential in order to avoid bias in the result.
+                    // * otherwise, we return A*MaxCnt+N
+                    //
+                    alglib.ap.assert(n/maxcnt+1<=maxcnt, "HQRNDUniformI: N is too large");
+                    result = -1;
+                    do
+                    {
+                        a = hqrnduniformi(state, maxcnt, _params);
+                        b = hqrnduniformi(state, n/maxcnt+1, _params);
+                        if( b==n/maxcnt && a>=n%maxcnt )
+                        {
+                            continue;
+                        }
+                        result = a+maxcnt*b;
+                    }
+                    while( result<0 );
+                }
+            }
+            else
+            {
+                
+                //
+                // N<=MaxCnt
+                //
+                // Code below is a bit complicated because we can not simply
+                // return "HQRNDIntegerBase() mod N" - it will be skewed for
+                // large N's in [0.1*HQRNDMax...HQRNDMax].
+                //
+                mx = maxcnt-maxcnt%n;
+                do
+                {
+                    result = hqrndintegerbase(state, _params);
+                }
+                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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double v1 = 0;
+            double v2 = 0;
+
+            hqrndnormal2(state, ref v1, ref v2, _params);
+            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,
+            alglib.xparams _params)
+        {
+            double v = 0;
+            double mx = 0;
+            double mn = 0;
+
+            x = 0;
+            y = 0;
+
+            do
+            {
+                hqrndnormal2(state, ref x, ref y, _params);
+            }
+            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,
+            alglib.xparams _params)
+        {
+            double u = 0;
+            double v = 0;
+            double s = 0;
+
+            x1 = 0;
+            x2 = 0;
+
+            while( true )
+            {
+                u = 2*hqrnduniformr(state, _params)-1;
+                v = 2*hqrnduniformr(state, _params)-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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert((double)(lambdav)>(double)(0), "HQRNDExponential: LambdaV<=0!");
+            result = -(Math.Log(hqrnduniformr(state, _params))/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,
+            alglib.xparams _params)
+        {
+            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, _params)];
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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, _params)+mn;
+            }
+            else
+            {
+                result = mn;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns random integer in [0,HQRNDMax]
+
+        L'Ecuyer, Efficient and portable combined random number generators
+        *************************************************************************/
+        private static int hqrndintegerbase(hqrndstate state,
+            alglib.xparams _params)
+        {
+            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;
+            }
+            result = result-1;
+            return result;
+        }
+
+
+    }
+    public class xdebug
+    {
+        public class xdebugrecord1 : apobject
+        {
+            public int i;
+            public complex c;
+            public double[] a;
+            public xdebugrecord1()
+            {
+                init();
+            }
+            public override void init()
+            {
+                a = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                xdebugrecord1 _result = new xdebugrecord1();
+                _result.i = i;
+                _result.c = c;
+                _result.a = (double[])a.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Creates and returns XDebugRecord1 structure:
+        * integer and complex fields of Rec1 are set to 1 and 1+i correspondingly
+        * array field of Rec1 is set to [2,3]
+
+          -- ALGLIB --
+             Copyright 27.05.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebuginitrecord1(xdebugrecord1 rec1,
+            alglib.xparams _params)
+        {
+            rec1.i = 1;
+            rec1.c.x = 1;
+            rec1.c.y = 1;
+            rec1.a = new double[2];
+            rec1.a[0] = 2;
+            rec1.a[1] = 3;
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Counts number of True values in the boolean 1D array.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static int xdebugb1count(bool[] a,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+
+            result = 0;
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                if( a[i] )
+                {
+                    result = result+1;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Replace all values in array by NOT(a[i]).
+        Array is passed using "shared" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugb1not(bool[] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                a[i] = !a[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Appends copy of array to itself.
+        Array is passed using "var" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugb1appendcopy(ref bool[] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            bool[] b = new bool[0];
+
+            b = new bool[alglib.ap.len(a)];
+            for(i=0; i<=alglib.ap.len(b)-1; i++)
+            {
+                b[i] = a[i];
+            }
+            a = new bool[2*alglib.ap.len(b)];
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                a[i] = b[i%alglib.ap.len(b)];
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Generate N-element array with even-numbered elements set to True.
+        Array is passed using "out" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugb1outeven(int n,
+            ref bool[] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            a = new bool[0];
+
+            a = new bool[n];
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                a[i] = i%2==0;
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Returns sum of elements in the array.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static int xdebugi1sum(int[] a,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+
+            result = 0;
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                result = result+a[i];
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Replace all values in array by -A[I]
+        Array is passed using "shared" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugi1neg(int[] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                a[i] = -a[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Appends copy of array to itself.
+        Array is passed using "var" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugi1appendcopy(ref int[] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int[] b = new int[0];
+
+            b = new int[alglib.ap.len(a)];
+            for(i=0; i<=alglib.ap.len(b)-1; i++)
+            {
+                b[i] = a[i];
+            }
+            a = new int[2*alglib.ap.len(b)];
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                a[i] = b[i%alglib.ap.len(b)];
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Generate N-element array with even-numbered A[I] set to I, and odd-numbered
+        ones set to 0.
+
+        Array is passed using "out" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugi1outeven(int n,
+            ref int[] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            a = new int[0];
+
+            a = new int[n];
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                if( i%2==0 )
+                {
+                    a[i] = i;
+                }
+                else
+                {
+                    a[i] = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Returns sum of elements in the array.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static double xdebugr1sum(double[] a,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int i = 0;
+
+            result = 0;
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                result = result+a[i];
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Replace all values in array by -A[I]
+        Array is passed using "shared" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugr1neg(double[] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                a[i] = -a[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Appends copy of array to itself.
+        Array is passed using "var" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugr1appendcopy(ref double[] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double[] b = new double[0];
+
+            b = new double[alglib.ap.len(a)];
+            for(i=0; i<=alglib.ap.len(b)-1; i++)
+            {
+                b[i] = a[i];
+            }
+            a = new double[2*alglib.ap.len(b)];
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                a[i] = b[i%alglib.ap.len(b)];
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Generate N-element array with even-numbered A[I] set to I*0.25,
+        and odd-numbered ones are set to 0.
+
+        Array is passed using "out" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugr1outeven(int n,
+            ref double[] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            a = new double[0];
+
+            a = new double[n];
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                if( i%2==0 )
+                {
+                    a[i] = i*0.25;
+                }
+                else
+                {
+                    a[i] = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Returns sum of elements in the array.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static complex xdebugc1sum(complex[] a,
+            alglib.xparams _params)
+        {
+            complex result = 0;
+            int i = 0;
+
+            result = 0;
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                result = result+a[i];
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Replace all values in array by -A[I]
+        Array is passed using "shared" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugc1neg(complex[] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                a[i] = -a[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Appends copy of array to itself.
+        Array is passed using "var" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugc1appendcopy(ref complex[] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            complex[] b = new complex[0];
+
+            b = new complex[alglib.ap.len(a)];
+            for(i=0; i<=alglib.ap.len(b)-1; i++)
+            {
+                b[i] = a[i];
+            }
+            a = new complex[2*alglib.ap.len(b)];
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                a[i] = b[i%alglib.ap.len(b)];
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Generate N-element array with even-numbered A[K] set to (x,y) = (K*0.25, K*0.125)
+        and odd-numbered ones are set to 0.
+
+        Array is passed using "out" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugc1outeven(int n,
+            ref complex[] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            a = new complex[0];
+
+            a = new complex[n];
+            for(i=0; i<=alglib.ap.len(a)-1; i++)
+            {
+                if( i%2==0 )
+                {
+                    a[i].x = i*0.250;
+                    a[i].y = i*0.125;
+                }
+                else
+                {
+                    a[i] = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Counts number of True values in the boolean 2D array.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static int xdebugb2count(bool[,] a,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+            int j = 0;
+
+            result = 0;
+            for(i=0; i<=alglib.ap.rows(a)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(a)-1; j++)
+                {
+                    if( a[i,j] )
+                    {
+                        result = result+1;
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Replace all values in array by NOT(a[i]).
+        Array is passed using "shared" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugb2not(bool[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            for(i=0; i<=alglib.ap.rows(a)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(a)-1; j++)
+                {
+                    a[i,j] = !a[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Transposes array.
+        Array is passed using "var" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugb2transpose(ref bool[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            bool[,] b = new bool[0,0];
+
+            b = new bool[alglib.ap.rows(a), alglib.ap.cols(a)];
+            for(i=0; i<=alglib.ap.rows(b)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(b)-1; j++)
+                {
+                    b[i,j] = a[i,j];
+                }
+            }
+            a = new bool[alglib.ap.cols(b), alglib.ap.rows(b)];
+            for(i=0; i<=alglib.ap.rows(b)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(b)-1; j++)
+                {
+                    a[j,i] = b[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Generate MxN matrix with elements set to "Sin(3*I+5*J)>0"
+        Array is passed using "out" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugb2outsin(int m,
+            int n,
+            ref bool[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            a = new bool[0,0];
+
+            a = new bool[m, n];
+            for(i=0; i<=alglib.ap.rows(a)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(a)-1; j++)
+                {
+                    a[i,j] = (double)(Math.Sin(3*i+5*j))>(double)(0);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Returns sum of elements in the array.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static int xdebugi2sum(int[,] a,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+            int j = 0;
+
+            result = 0;
+            for(i=0; i<=alglib.ap.rows(a)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(a)-1; j++)
+                {
+                    result = result+a[i,j];
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Replace all values in array by -a[i,j]
+        Array is passed using "shared" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugi2neg(int[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            for(i=0; i<=alglib.ap.rows(a)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(a)-1; j++)
+                {
+                    a[i,j] = -a[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Transposes array.
+        Array is passed using "var" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugi2transpose(ref int[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int[,] b = new int[0,0];
+
+            b = new int[alglib.ap.rows(a), alglib.ap.cols(a)];
+            for(i=0; i<=alglib.ap.rows(b)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(b)-1; j++)
+                {
+                    b[i,j] = a[i,j];
+                }
+            }
+            a = new int[alglib.ap.cols(b), alglib.ap.rows(b)];
+            for(i=0; i<=alglib.ap.rows(b)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(b)-1; j++)
+                {
+                    a[j,i] = b[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Generate MxN matrix with elements set to "Sign(Sin(3*I+5*J))"
+        Array is passed using "out" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugi2outsin(int m,
+            int n,
+            ref int[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            a = new int[0,0];
+
+            a = new int[m, n];
+            for(i=0; i<=alglib.ap.rows(a)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(a)-1; j++)
+                {
+                    a[i,j] = Math.Sign(Math.Sin(3*i+5*j));
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Returns sum of elements in the array.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static double xdebugr2sum(double[,] a,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+
+            result = 0;
+            for(i=0; i<=alglib.ap.rows(a)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(a)-1; j++)
+                {
+                    result = result+a[i,j];
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Replace all values in array by -a[i,j]
+        Array is passed using "shared" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugr2neg(double[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            for(i=0; i<=alglib.ap.rows(a)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(a)-1; j++)
+                {
+                    a[i,j] = -a[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Transposes array.
+        Array is passed using "var" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugr2transpose(ref double[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double[,] b = new double[0,0];
+
+            b = new double[alglib.ap.rows(a), alglib.ap.cols(a)];
+            for(i=0; i<=alglib.ap.rows(b)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(b)-1; j++)
+                {
+                    b[i,j] = a[i,j];
+                }
+            }
+            a = new double[alglib.ap.cols(b), alglib.ap.rows(b)];
+            for(i=0; i<=alglib.ap.rows(b)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(b)-1; j++)
+                {
+                    a[j,i] = b[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Generate MxN matrix with elements set to "Sin(3*I+5*J)"
+        Array is passed using "out" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugr2outsin(int m,
+            int n,
+            ref double[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            a = new double[0,0];
+
+            a = new double[m, n];
+            for(i=0; i<=alglib.ap.rows(a)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(a)-1; j++)
+                {
+                    a[i,j] = Math.Sin(3*i+5*j);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Returns sum of elements in the array.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static complex xdebugc2sum(complex[,] a,
+            alglib.xparams _params)
+        {
+            complex result = 0;
+            int i = 0;
+            int j = 0;
+
+            result = 0;
+            for(i=0; i<=alglib.ap.rows(a)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(a)-1; j++)
+                {
+                    result = result+a[i,j];
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Replace all values in array by -a[i,j]
+        Array is passed using "shared" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugc2neg(complex[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            for(i=0; i<=alglib.ap.rows(a)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(a)-1; j++)
+                {
+                    a[i,j] = -a[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Transposes array.
+        Array is passed using "var" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugc2transpose(ref complex[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            complex[,] b = new complex[0,0];
+
+            b = new complex[alglib.ap.rows(a), alglib.ap.cols(a)];
+            for(i=0; i<=alglib.ap.rows(b)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(b)-1; j++)
+                {
+                    b[i,j] = a[i,j];
+                }
+            }
+            a = new complex[alglib.ap.cols(b), alglib.ap.rows(b)];
+            for(i=0; i<=alglib.ap.rows(b)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(b)-1; j++)
+                {
+                    a[j,i] = b[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Generate MxN matrix with elements set to "Sin(3*I+5*J),Cos(3*I+5*J)"
+        Array is passed using "out" convention.
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void xdebugc2outsincos(int m,
+            int n,
+            ref complex[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            a = new complex[0,0];
+
+            a = new complex[m, n];
+            for(i=0; i<=alglib.ap.rows(a)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(a)-1; j++)
+                {
+                    a[i,j].x = Math.Sin(3*i+5*j);
+                    a[i,j].y = Math.Cos(3*i+5*j);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This is debug function intended for testing ALGLIB interface generator.
+        Never use it in any real life project.
+
+        Returns sum of a[i,j]*(1+b[i,j]) such that c[i,j] is True
+
+          -- ALGLIB --
+             Copyright 11.10.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static double xdebugmaskedbiasedproductsum(int m,
+            int n,
+            double[,] a,
+            double[,] b,
+            bool[,] c,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(m>=alglib.ap.rows(a));
+            alglib.ap.assert(m>=alglib.ap.rows(b));
+            alglib.ap.assert(m>=alglib.ap.rows(c));
+            alglib.ap.assert(n>=alglib.ap.cols(a));
+            alglib.ap.assert(n>=alglib.ap.cols(b));
+            alglib.ap.assert(n>=alglib.ap.cols(c));
+            result = 0.0;
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( c[i,j] )
+                    {
+                        result = result+a[i,j]*(1+b[i,j]);
+                    }
+                }
+            }
+            return result;
+        }
+
+
+    }
+}
+
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/ap.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/ap.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/ap.cs	(revision 17158)
@@ -0,0 +1,2253 @@
+/**************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 >>>
+**************************************************************************/
+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;
+        }
+    }
+    
+    /********************************************************************
+    Critical failure, resilts in immediate termination of entire program.
+    ********************************************************************/
+    public static void AE_CRITICAL_ASSERT(bool x)
+    {
+        if( !x )
+            System.Environment.FailFast("ALGLIB: critical error");
+    }
+    
+    /********************************************************************
+    ALGLIB object, parent  class  for  all  internal  AlgoPascal  objects
+    managed by ALGLIB.
+    
+    Any internal AlgoPascal object inherits from this class.
+    
+    User-visible objects inherit from alglibobject (see below).
+    ********************************************************************/
+    public abstract class apobject
+    {
+        public abstract void init();
+        public abstract apobject make_copy();
+    }
+    
+    /********************************************************************
+    ALGLIB object, parent class for all user-visible objects  managed  by
+    ALGLIB.
+    
+    Methods:
+        _deallocate()       deallocation:
+                            * in managed ALGLIB it does nothing
+                            * in native ALGLIB it clears  dynamic  memory
+                              being  hold  by  object  and  sets internal
+                              reference to null.
+        make_copy()         creates deep copy of the object.
+                            Works in both managed and native versions  of
+                            ALGLIB.
+    ********************************************************************/
+    public abstract class alglibobject : IDisposable
+    {
+        public virtual void _deallocate() {}
+        public abstract alglibobject make_copy();
+        public void Dispose()
+        {
+            _deallocate();
+        }
+    }
+    
+    /********************************************************************
+    xparams object, used to pass additional parameters like multithreading
+    settings, and several predefined values
+    ********************************************************************/
+    public class xparams
+    {
+        public ulong flags;
+        public xparams(ulong v)
+        {
+            flags = v;
+        }
+    }
+    private static ulong FLG_THREADING_MASK          = 0x7;
+    private static   int FLG_THREADING_SHIFT         = 0;
+    private static ulong FLG_THREADING_USE_GLOBAL    = 0x0;
+    private static ulong FLG_THREADING_SERIAL        = 0x1;
+    private static ulong FLG_THREADING_PARALLEL      = 0x2;
+    public static xparams serial   = new xparams(FLG_THREADING_SERIAL);
+    public static xparams parallel = new xparams(FLG_THREADING_PARALLEL);
+
+    /********************************************************************
+    Global flags, split into several char-sized variables in order
+    to avoid problem with non-atomic reads/writes (single-byte ops
+    are atomic on all modern architectures);
+    
+    Following variables are included:
+    * threading-related settings
+    ********************************************************************/
+    public static byte global_threading_flags = (byte)(FLG_THREADING_SERIAL>>FLG_THREADING_SHIFT);
+    
+    public static void setglobalthreading(xparams p)
+    {
+        AE_CRITICAL_ASSERT(p!=null);
+        ae_set_global_threading(p.flags);
+    }
+    
+    public static void ae_set_global_threading(ulong flg_value)
+    {
+        flg_value = flg_value&FLG_THREADING_MASK;
+        AE_CRITICAL_ASSERT(flg_value==FLG_THREADING_SERIAL || flg_value==FLG_THREADING_PARALLEL);
+        global_threading_flags = (byte)(flg_value>>FLG_THREADING_SHIFT);
+    }
+    
+    public static ulong ae_get_global_threading()
+    {
+        return ((ulong)global_threading_flags)<<FLG_THREADING_SHIFT;
+    }
+    
+    static ulong ae_get_effective_threading(xparams p)
+    {
+        if( p==null || (p.flags&FLG_THREADING_MASK)==FLG_THREADING_USE_GLOBAL )
+            return ((ulong)global_threading_flags)<<FLG_THREADING_SHIFT;
+        return p.flags&FLG_THREADING_MASK;
+    }
+    
+    /********************************************************************
+    Deallocation of ALGLIB object:
+    * in managed ALGLIB this method just sets refence to null
+    * in native ALGLIB call of this method:
+      1) clears dynamic memory being hold by  object  and  sets  internal
+         reference to null.
+      2) sets to null variable being passed to this method
+    
+    IMPORTANT (1): in  native  edition  of  ALGLIB,  obj becomes unusable
+                   after this call!!!  It  is  possible  to  save  a copy
+                   of reference in another variable (original variable is
+                   set to null), but any attempt to work with this object
+                   will crash your program.
+    
+    IMPORTANT (2): memory owned by object will be recycled by GC  in  any
+                   case. This method just enforces IMMEDIATE deallocation.
+    ********************************************************************/
+    public static void deallocateimmediately<T>(ref T obj) where T : alglib.alglibobject
+    {
+        obj._deallocate();
+        obj = null;
+    }
+
+    /********************************************************************
+    Allocation counter:
+    * in managed ALGLIB it always returns 0 (dummy code)
+    * in native ALGLIB it returns current value of the allocation counter
+      (if it was activated)
+    ********************************************************************/
+    public static long alloc_counter()
+    {
+        return 0;
+    }
+    
+    /********************************************************************
+    Activization of the allocation counter:
+    * in managed ALGLIB it does nothing (dummy code)
+    * in native ALGLIB it turns on allocation counting.
+    ********************************************************************/
+    public static void alloc_counter_activate()
+    {
+    }
+    
+    /********************************************************************
+    This function allows to set one of the debug flags.
+    In managed ALGLIB does nothing (dummy).
+    ********************************************************************/
+    public static void set_dbg_flag(long flag_id, long flag_value)
+    {
+    }
+    
+    /********************************************************************
+    This function allows to get one of the debug counters.
+    In managed ALGLIB does nothing (dummy).
+    ********************************************************************/
+    public static long get_dbg_value(long id)
+    {
+        return 0;
+    }
+    
+    /********************************************************************
+    Activization of the allocation counter:
+    * in managed ALGLIB it does nothing (dummy code)
+    * in native ALGLIB it turns on allocation counting.
+    ********************************************************************/
+    public static void free_disposed_items()
+    {
+    }
+    
+    /************************************************************************
+    This function maps nworkers  number  (which  can  be  positive,  zero  or
+    negative with 0 meaning "all cores", -1 meaning "all cores -1" and so on)
+    to "effective", strictly positive workers count.
+
+    This  function  is  intended  to  be used by debugging/testing code which
+    tests different number of worker threads. It is NOT aligned  in  any  way
+    with ALGLIB multithreading framework (i.e. it can return  non-zero worker
+    count even for single-threaded GPLed ALGLIB).
+    ************************************************************************/
+    public static int get_effective_workers(int nworkers)
+    {
+        int ncores = System.Environment.ProcessorCount;
+        if( nworkers>=1 )
+            return nworkers>ncores ? ncores : nworkers;
+        return ncores+nworkers>=1 ? ncores+nworkers : 1;
+    }
+    
+    /********************************************************************
+    reverse communication structure
+    ********************************************************************/
+    public class rcommstate : apobject
+    {
+        public rcommstate()
+        {
+            init();
+        }
+        public override void init()
+        {
+            stage = -1;
+            ia = new int[0];
+            ba = new bool[0];
+            ra = new double[0];
+            ca = new alglib.complex[0];
+        }
+        public override apobject make_copy()
+        {
+            rcommstate result = new rcommstate();
+            result.stage = stage;
+            result.ia = (int[])ia.Clone();
+            result.ba = (bool[])ba.Clone();
+            result.ra = (double[])ra.Clone();
+            result.ca = (alglib.complex[])ca.Clone();
+            return result;
+        }
+        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");
+        }
+        
+        /****************************************************************
+        Error tracking for unit testing purposes; utility functions.
+        ****************************************************************/
+        public static string sef_xdesc = "";
+        
+        public static void seterrorflag(ref bool flag, bool cond, string xdesc)
+        {
+            if( cond )
+            {
+                flag = true;
+                sef_xdesc = xdesc;
+            }
+        }
+        
+        /****************************************************************
+        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);
+    [ThreadStatic] //mkommend: added thread static attribute to RNG to have a separate instance per thread and allow modification of the seed
+    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;
+            if (rndobject == null) rndobject = new System.Random(System.DateTime.Now.Millisecond + 1000 * System.DateTime.Now.Second + 60 * 1000 * System.DateTime.Now.Minute);
+            lock(rndobject){ r = rndobject.NextDouble(); }
+            return r;
+        }
+        public static int randominteger(int N)
+        {
+            int r = 0;
+            if (rndobject == null) rndobject = new System.Random(System.DateTime.Now.Millisecond + 1000 * System.DateTime.Now.Second + 60 * 1000 * System.DateTime.Now.Minute);
+            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); 
+        }
+
+    }
+
+    /*
+     * CSV functionality
+     */
+     
+    public static int CSV_DEFAULT      = 0x0;
+    public static int CSV_SKIP_HEADERS = 0x1;
+    
+    /*
+     * CSV operations: reading CSV file to real matrix.
+     * 
+     * This function reads CSV  file  and  stores  its  contents  to  double
+     * precision 2D array. Format of the data file must conform to RFC  4180
+     * specification, with additional notes:
+     * - file size should be less than 2GB
+     * - ASCI encoding, UTF-8 without BOM (in header names) are supported
+     * - any character (comma/tab/space) may be used as field separator,  as
+     *   long as it is distinct from one used for decimal point
+     * - multiple subsequent field separators (say, two  spaces) are treated
+     *   as MULTIPLE separators, not one big separator
+     * - both comma and full stop may be used as decimal point. Parser  will
+     *   automatically determine specific character being used.  Both  fixed
+     *   and exponential number formats are  allowed.   Thousand  separators
+     *   are NOT allowed.
+     * - line may end with \n (Unix style) or \r\n (Windows  style),  parser
+     *   will automatically adapt to chosen convention
+     * - escaped fields (ones in double quotes) are not supported
+     * 
+     * INPUT PARAMETERS:
+     *     filename        relative/absolute path
+     *     separator       character used to separate fields.  May  be  ' ',
+     *                     ',', '\t'. Other separators are possible too.
+     *     flags           several values combined with bitwise OR:
+     *                     * alglib::CSV_SKIP_HEADERS -  if present, first row
+     *                       contains headers  and  will  be  skipped.   Its
+     *                       contents is used to determine fields count, and
+     *                       that's all.
+     *                     If no flags are specified, default value 0x0  (or
+     *                     alglib::CSV_DEFAULT, which is same) should be used.
+     *                     
+     * OUTPUT PARAMETERS:
+     *     out             2D matrix, CSV file parsed with atof()
+     *     
+     * HANDLING OF SPECIAL CASES:
+     * - file does not exist - alglib::ap_error exception is thrown
+     * - empty file - empty array is returned (no exception)
+     * - skip_first_row=true, only one row in file - empty array is returned
+     * - field contents is not recognized by atof() - field value is replaced
+     *   by 0.0
+     */
+    public static void read_csv(string filename, char separator, int flags, out double[,] matrix)
+    {
+        //
+        // Parameters
+        //
+        bool skip_first_row = (flags&CSV_SKIP_HEADERS)!=0;
+        
+        //
+        // Prepare empty output array
+        //
+        matrix = new double[0,0];
+        
+        //
+        // Read file, normalize file contents:
+        // * replace 0x0 by spaces
+        // * remove trailing spaces and newlines
+        // * append trailing '\n' and '\0' characters
+        // Return if file contains only spaces/newlines.
+        //
+        byte b_space = System.Convert.ToByte(' ');
+        byte b_tab   = System.Convert.ToByte('\t');
+        byte b_lf    = System.Convert.ToByte('\n');
+        byte b_cr    = System.Convert.ToByte('\r');
+        byte b_comma = System.Convert.ToByte(',');
+        byte b_fullstop= System.Convert.ToByte('.');
+        byte[] v0 = System.IO.File.ReadAllBytes(filename);
+        if( v0.Length==0 )
+            return;
+        byte[] v1 = new byte[v0.Length+2];
+        int filesize = v0.Length;
+        for(int i=0; i<filesize; i++)
+            v1[i] = v0[i]==0 ? b_space : v0[i];
+        for(; filesize>0; )
+        {
+            byte c = v1[filesize-1];
+            if( c==b_space || c==b_tab || c==b_cr || c==b_lf )
+            {
+                filesize--;
+                continue;
+            }
+            break;
+        }
+        if( filesize==0 )
+            return;
+        v1[filesize+0] = b_lf;
+        v1[filesize+1] = 0x0;
+        filesize+=2;
+        
+        
+        //
+        // Scan dataset.
+        //
+        int rows_count, cols_count, max_length = 0;
+        cols_count = 1;
+        for(int idx=0; idx<filesize; idx++)
+        {
+            if( v1[idx]==separator )
+                cols_count++;
+            if( v1[idx]==b_lf )
+                break;
+        }
+        rows_count = 0;
+        for(int idx=0; idx<filesize; idx++)
+            if( v1[idx]==b_lf )
+                rows_count++;
+        if( rows_count==1 && skip_first_row ) // empty output, return
+            return;
+        int[] offsets = new int[rows_count*cols_count];
+        int[] lengths = new int[rows_count*cols_count];
+        int cur_row_idx = 0;
+        for(int row_start=0; v1[row_start]!=0x0; )
+        {
+            // determine row length
+            int row_length;
+            for(row_length=0; v1[row_start+row_length]!=b_lf; row_length++);
+            
+            // determine cols count, perform integrity check
+            int cur_cols_cnt=1;
+            for(int idx=0; idx<row_length; idx++)
+                if( v1[row_start+idx]==separator )
+                    cur_cols_cnt++;
+            if( cols_count!=cur_cols_cnt )
+                throw new alglib.alglibexception("read_csv: non-rectangular contents, rows have different sizes");
+            
+            // store offsets and lengths of the fields
+            int cur_offs = 0;
+            int cur_col_idx = 0;
+            for(int idx=0; idx<row_length+1; idx++)
+                if( v1[row_start+idx]==separator || v1[row_start+idx]==b_lf )
+                {
+                    offsets[cur_row_idx*cols_count+cur_col_idx] = row_start+cur_offs;
+                    lengths[cur_row_idx*cols_count+cur_col_idx] = idx-cur_offs;
+                    max_length = idx-cur_offs>max_length ? idx-cur_offs : max_length;
+                    cur_offs = idx+1;
+                    cur_col_idx++;
+                }
+            
+            // advance row start
+            cur_row_idx++;
+            row_start = row_start+row_length+1;
+        }
+        
+        //
+        // Convert
+        //
+        int row0 = skip_first_row ? 1 : 0;
+        int row1 = rows_count;
+        System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CreateSpecificCulture(""); // invariant culture
+        matrix = new double[row1-row0, cols_count];
+        alglib.AE_CRITICAL_ASSERT(culture.NumberFormat.NumberDecimalSeparator==".");
+        for(int ridx=row0; ridx<row1; ridx++)
+            for(int cidx=0; cidx<cols_count; cidx++)
+            {
+                int field_len  = lengths[ridx*cols_count+cidx];
+                int field_offs = offsets[ridx*cols_count+cidx];
+                
+                // replace , by full stop
+                for(int idx=0; idx<field_len; idx++)
+                    if( v1[field_offs+idx]==b_comma )
+                        v1[field_offs+idx] = b_fullstop;
+                
+                // convert
+                string s_val = System.Text.Encoding.ASCII.GetString(v1, field_offs, field_len);
+                double d_val;
+                Double.TryParse(s_val, System.Globalization.NumberStyles.Float, culture, out d_val);
+                matrix[ridx-row0,cidx] = d_val;
+            }
+    }
+    
+    
+    /********************************************************************
+    serializer object (should not be used directly)
+    ********************************************************************/
+    public class serializer
+    {
+        enum SMODE { DEFAULT, ALLOC, TO_STRING, FROM_STRING, TO_STREAM, FROM_STREAM };
+        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;
+        private System.IO.Stream io_stream;
+        
+        // local temporaries
+        private char[] entry_buf_char;
+        private byte[] entry_buf_byte; 
+        
+        public serializer()
+        {
+            mode = SMODE.DEFAULT;
+            entries_needed = 0;
+            bytes_asked = 0;
+            entry_buf_byte = new byte[SER_ENTRY_LENGTH+2];
+            entry_buf_char = new char[SER_ENTRY_LENGTH+2];
+        }
+
+        public void clear_buffers()
+        {
+            out_str = null;
+            in_str = null;
+            io_stream = null;
+        }
+
+        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 = 4; /* a pair of chars for \r\n, one for space, one for dot */ 
+                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;  /* data size */
+            result +=  (rows-1)*(SER_ENTRIES_PER_ROW-1)+(lastrowsize-1);            /* space symbols */
+            result += rows*2;                                                       /* newline symbols */
+            result += 1;                                                            /* trailing dot */
+            bytes_asked = result;
+            return result;
+        }
+
+        public void sstart_str()
+        {
+            int allocsize = get_alloc_size();
+            
+            // clear input/output buffers which may hold pointers to unneeded memory
+            // NOTE: it also helps us to avoid errors when data are written to incorrect location
+            clear_buffers();
+            
+            // 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 sstart_stream(System.IO.Stream o_stream)
+        {   
+            // clear input/output buffers which may hold pointers to unneeded memory
+            // NOTE: it also helps us to avoid errors when data are written to incorrect location
+            clear_buffers();
+            
+            // check and change mode
+            if( mode!=SMODE.ALLOC )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+            mode = SMODE.TO_STREAM;
+            io_stream = o_stream;
+        }
+
+        public void ustart_str(string s)
+        {
+            // clear input/output buffers which may hold pointers to unneeded memory
+            // NOTE: it also helps us to avoid errors when data are written to incorrect location
+            clear_buffers();
+            
+            // 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 ustart_stream(System.IO.Stream i_stream)
+        {
+            // clear input/output buffers which may hold pointers to unneeded memory
+            // NOTE: it also helps us to avoid errors when data are written to incorrect location
+            clear_buffers();
+            
+            // check and change mode
+            if( mode!=SMODE.DEFAULT )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+            mode = SMODE.FROM_STREAM;
+            io_stream = i_stream;
+        }
+
+        private void serialize_value(bool v0, int v1, double v2, int val_idx)
+        {
+            // prepare serialization
+            char[] arr_out = null;
+            int cnt_out = 0;
+            if( mode==SMODE.TO_STRING )
+            {
+                arr_out = out_str;
+                cnt_out = bytes_written;
+            }
+            else if( mode==SMODE.TO_STREAM )
+            {
+                arr_out = entry_buf_char;
+                cnt_out = 0;
+            }
+            else
+                throw new alglib.alglibexception("ALGLIB: internal error during serialization");
+            
+            // serialize
+            if( val_idx==0 )
+                bool2str(  v0, arr_out, ref cnt_out);
+            else if( val_idx==1 )
+                int2str(   v1, arr_out, ref cnt_out);
+            else if( val_idx==2 )
+                double2str(v2, arr_out, ref cnt_out);
+            else
+                throw new alglib.alglibexception("ALGLIB: internal error during serialization");
+            entries_saved++;
+            if( entries_saved%SER_ENTRIES_PER_ROW!=0 )
+            {
+                arr_out[cnt_out] = ' ';
+                cnt_out++;
+            }
+            else
+            {
+                arr_out[cnt_out+0] = '\r';
+                arr_out[cnt_out+1] = '\n';
+                cnt_out+=2;
+            }
+            
+            // post-process
+            if( mode==SMODE.TO_STRING )
+            {
+                bytes_written = cnt_out;
+                return;
+            }
+            else if( mode==SMODE.TO_STREAM )
+            {
+                for(int k=0; k<cnt_out; k++)
+                    entry_buf_byte[k] = (byte)entry_buf_char[k];
+                io_stream.Write(entry_buf_byte, 0, cnt_out);
+                return;
+            }
+            else
+                throw new alglib.alglibexception("ALGLIB: internal error during serialization");
+        }
+
+        private void unstream_entry_char()
+        {
+            if( mode!=SMODE.FROM_STREAM )
+                throw new alglib.alglibexception("ALGLIB: internal error during unserialization");
+            int c;
+            for(;;)
+            {
+                c = io_stream.ReadByte();
+                if( c<0 )
+                    throw new alglib.alglibexception("ALGLIB: internal error during unserialization");
+                if( c!=' ' && c!='\t' && c!='\n' && c!='\r' )
+                    break;
+            }
+            entry_buf_char[0] = (char)c;
+            for(int k=1; k<SER_ENTRY_LENGTH; k++)
+            {
+                c = io_stream.ReadByte();
+                entry_buf_char[k] = (char)c;
+                if( c<0 || c==' ' || c=='\t' || c=='\n' || c=='\r' )
+                    throw new alglib.alglibexception("ALGLIB: internal error during unserialization");
+            }
+            entry_buf_char[SER_ENTRY_LENGTH] = (char)0;
+        }
+
+        public void serialize_bool(bool v)
+        {
+            serialize_value(v, 0, 0, 0);
+        }
+
+        public void serialize_int(int v)
+        {
+            serialize_value(false, v, 0, 1);
+        }
+
+        public void serialize_double(double v)
+        {
+            serialize_value(false, 0, v, 2);
+        }
+
+        public bool unserialize_bool()
+        {
+            if( mode==SMODE.FROM_STRING )
+                return str2bool(in_str, ref bytes_read);
+            if( mode==SMODE.FROM_STREAM )
+            {
+                unstream_entry_char();
+                int dummy = 0;
+                return str2bool(entry_buf_char, ref dummy);
+            }
+            throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+        }
+
+        public int unserialize_int()
+        {
+            if( mode==SMODE.FROM_STRING )
+                return str2int(in_str, ref bytes_read);
+            if( mode==SMODE.FROM_STREAM )
+            {
+                unstream_entry_char();
+                int dummy = 0;
+                return str2int(entry_buf_char, ref dummy);
+            }
+            throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+        }
+
+        public double unserialize_double()
+        {
+            if( mode==SMODE.FROM_STRING )
+                return str2double(in_str, ref bytes_read);
+            if( mode==SMODE.FROM_STREAM )
+            {
+                unstream_entry_char();
+                int dummy = 0;
+                return str2double(entry_buf_char, ref dummy);
+            }
+            throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+        }
+
+        public void stop()
+        {
+            if( mode==SMODE.TO_STRING )
+            {
+                out_str[bytes_written] = '.';
+                bytes_written++;
+                return;
+            }
+            if( mode==SMODE.FROM_STRING )
+            {
+                //
+                // because input string may be from pre-3.11 serializer,
+                // which does not include trailing dot, we do not test
+                // string for presence of "." symbol. Anyway, because string
+                // is not stream, we do not have to read ALL trailing symbols.
+                //
+                return;
+            }
+            if( mode==SMODE.TO_STREAM )
+            {
+                io_stream.WriteByte((byte)'.');
+                return;
+            }
+            if( mode==SMODE.FROM_STREAM )
+            {
+                for(;;)
+                {
+                    int c = io_stream.ReadByte();
+                    if( c==' ' || c=='\t' || c=='\n' || c=='\r' )
+                        continue;
+                    if( c=='.' )
+                        break;
+                    throw new alglib.alglibexception("ALGLIB: internal error during unserialization");
+                }
+                return;
+            }
+            throw new alglib.alglibexception("ALGLIB: internal error during unserialization");
+        }
+
+        public string get_string()
+        {
+            if( mode!=SMODE.TO_STRING )
+                throw new alglib.alglibexception("ALGLIB: internal error during (un)serialization");
+             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);
+        }
+    }
+    
+    /*
+     * Parts of alglib.smp class which are shared with GPL version of ALGLIB
+     */
+    public partial class smp
+    {
+        #pragma warning disable 420
+        public const int AE_LOCK_CYCLES = 512;
+        public const int AE_LOCK_TESTS_BEFORE_YIELD = 16;
+        
+        /*
+         * This variable is used to perform spin-wait loops in a platform-independent manner
+         * (loops which should work same way on Mono and Microsoft NET). You SHOULD NEVER
+         * change this field - it must be zero during all program life.
+         */
+        public static volatile int never_change_it = 0;
+        
+        /*************************************************************************
+        Lock.
+
+        This class provides lightweight spin lock
+        *************************************************************************/
+        public class ae_lock
+        {
+            public volatile int is_locked;
+        }
+
+        /********************************************************************
+        Shared pool: data structure used to provide thread-safe access to pool
+        of temporary variables.
+        ********************************************************************/
+        public class sharedpoolentry
+        {
+            public apobject obj;
+            public sharedpoolentry next_entry;
+        }
+        public class shared_pool : apobject
+        {
+            /* lock object which protects pool */
+            public ae_lock pool_lock;
+    
+            /* seed object (used to create new instances of temporaries) */
+            public volatile apobject seed_object;
+            
+            /*
+             * list of recycled OBJECTS:
+             * 1. entries in this list store pointers to recycled objects
+             * 2. every time we retrieve object, we retrieve first entry from this list,
+             *    move it to recycled_entries and return its obj field to caller/
+             */
+            public volatile sharedpoolentry recycled_objects;
+            
+            /* 
+             * list of recycled ENTRIES:
+             * 1. this list holds entries which are not used to store recycled objects;
+             *    every time recycled object is retrieved, its entry is moved to this list.
+             * 2. every time object is recycled, we try to fetch entry for him from this list
+             *    before allocating it with malloc()
+             */
+            public volatile sharedpoolentry recycled_entries;
+            
+            /* enumeration pointer, points to current recycled object*/
+            public volatile sharedpoolentry enumeration_counter;
+            
+            /* constructor */
+            public shared_pool()
+            {
+                ae_init_lock(ref pool_lock);
+            }
+            
+            /* initializer - creation of empty pool */
+            public override void init()
+            {
+                seed_object = null;
+                recycled_objects = null;
+                recycled_entries = null;
+                enumeration_counter = null;
+            }
+            
+            /* copy constructor (it is NOT thread-safe) */
+            public override apobject make_copy()
+            {
+                sharedpoolentry ptr, buf;
+                shared_pool result = new shared_pool();
+                
+                /* create lock */
+                ae_init_lock(ref result.pool_lock);
+    
+                /* copy seed object */
+                if( seed_object!=null )
+                    result.seed_object = seed_object.make_copy();
+                
+                /*
+                 * copy recycled objects:
+                 * 1. copy to temporary list (objects are inserted to beginning, order is reversed)
+                 * 2. copy temporary list to output list (order is restored back to normal)
+                 */
+                buf = null;
+                for(ptr=recycled_objects; ptr!=null; ptr=ptr.next_entry)
+                {
+                    sharedpoolentry tmp = new sharedpoolentry();
+                    tmp.obj =  ptr.obj.make_copy();
+                    tmp.next_entry = buf;
+                    buf = tmp;
+                }
+                result.recycled_objects = null;
+                for(ptr=buf; ptr!=null;)
+                {
+                    sharedpoolentry next_ptr = ptr.next_entry;
+                    ptr.next_entry = result.recycled_objects;
+                    result.recycled_objects = ptr;
+                    ptr = next_ptr;
+                }
+    
+                /* recycled entries are not copied because they do not store any information */
+                result.recycled_entries = null;
+    
+                /* enumeration counter is reset on copying */
+                result.enumeration_counter = null;
+    
+                return result;
+            }
+        }
+        
+
+        /************************************************************************
+        This function performs given number of spin-wait iterations
+        ************************************************************************/
+        public static void ae_spin_wait(int cnt)
+        {
+            /*
+             * these strange operations with ae_never_change_it are necessary to
+             * prevent compiler optimization of the loop.
+             */
+            int i;
+            
+            /* very unlikely because no one will wait for such amount of cycles */
+            if( cnt>0x12345678 )
+                never_change_it = cnt%10;
+            
+            /* spin wait, test condition which will never be true */
+            for(i=0; i<cnt; i++)
+                if( never_change_it>0 )
+                    never_change_it--;
+        }
+
+
+        /************************************************************************
+        This function causes the calling thread to relinquish the CPU. The thread
+        is moved to the end of the queue and some other thread gets to run.
+        ************************************************************************/
+        public static void ae_yield()
+        {
+            System.Threading.Thread.Sleep(0);
+        }
+
+        /************************************************************************
+        This function initializes ae_lock structure and sets lock in a free mode.
+        ************************************************************************/
+        public static void ae_init_lock(ref ae_lock obj)
+        {
+            obj = new ae_lock();
+            obj.is_locked = 0;
+        }
+
+
+        /************************************************************************
+        This function acquires lock. In case lock is busy, we perform several
+        iterations inside tight loop before trying again.
+        ************************************************************************/
+        public static void ae_acquire_lock(ae_lock obj)
+        {
+            int cnt = 0;
+            for(;;)
+            {
+                if( System.Threading.Interlocked.CompareExchange(ref obj.is_locked, 1, 0)==0 )
+                    return;
+                ae_spin_wait(AE_LOCK_CYCLES);
+                cnt++;
+                if( cnt%AE_LOCK_TESTS_BEFORE_YIELD==0 )
+                    ae_yield();
+            }
+        }
+
+
+        /************************************************************************
+        This function releases lock.
+        ************************************************************************/
+        public static void ae_release_lock(ae_lock obj)
+        {
+            System.Threading.Interlocked.Exchange(ref obj.is_locked, 0);
+        }
+
+
+        /************************************************************************
+        This function frees ae_lock structure.
+        ************************************************************************/
+        public static void ae_free_lock(ref ae_lock obj)
+        {
+            obj = null;
+        }
+        
+        
+        /************************************************************************
+        This function returns True, if internal seed object was set.  It  returns
+        False for un-seeded pool.
+
+        dst                 destination pool (initialized by constructor function)
+
+        NOTE: this function is NOT thread-safe. It does not acquire pool lock, so
+              you should NOT call it when lock can be used by another thread.
+        ************************************************************************/
+        public static bool ae_shared_pool_is_initialized(shared_pool dst)
+        {
+            return dst.seed_object!=null;
+        }
+
+
+        /************************************************************************
+        This function sets internal seed object. All objects owned by the pool
+        (current seed object, recycled objects) are automatically freed.
+
+        dst                 destination pool (initialized by constructor function)
+        seed_object         new seed object
+
+        NOTE: this function is NOT thread-safe. It does not acquire pool lock, so
+              you should NOT call it when lock can be used by another thread.
+        ************************************************************************/
+        public static void ae_shared_pool_set_seed(shared_pool dst, alglib.apobject seed_object)
+        {
+            dst.seed_object = seed_object.make_copy();
+            dst.recycled_objects = null;
+            dst.enumeration_counter = null;
+        }
+
+
+        /************************************************************************
+        This  function  retrieves  a  copy  of  the seed object from the pool and
+        stores it to target variable.
+
+        pool                pool
+        obj                 target variable
+        
+        NOTE: this function IS thread-safe.  It  acquires  pool  lock  during its
+              operation and can be used simultaneously from several threads.
+        ************************************************************************/
+        public static void ae_shared_pool_retrieve<T>(shared_pool pool, ref T obj) where T : alglib.apobject
+        {
+            alglib.apobject new_obj;
+            
+            /* assert that pool was seeded */
+            alglib.ap.assert(pool.seed_object!=null, "ALGLIB: shared pool is not seeded, PoolRetrieve() failed");
+            
+            /* acquire lock */
+            ae_acquire_lock(pool.pool_lock);
+            
+            /* try to reuse recycled objects */
+            if( pool.recycled_objects!=null )
+            {
+                /* retrieve entry/object from list of recycled objects */
+                sharedpoolentry result = pool.recycled_objects;
+                pool.recycled_objects = pool.recycled_objects.next_entry;
+                new_obj = result.obj;
+                result.obj = null;
+                
+                /* move entry to list of recycled entries */
+                result.next_entry = pool.recycled_entries;
+                pool.recycled_entries = result;
+                
+                /* release lock */
+                ae_release_lock(pool.pool_lock);
+                
+                /* assign object to smart pointer */
+                obj = (T)new_obj;
+                
+                return;
+            }
+                
+            /*
+             * release lock; we do not need it anymore because
+             * copy constructor does not modify source variable.
+             */
+            ae_release_lock(pool.pool_lock);
+            
+            /* create new object from seed */
+            new_obj = pool.seed_object.make_copy();
+                
+            /* assign object to pointer and return */
+            obj = (T)new_obj;
+        }
+
+
+        /************************************************************************
+        This  function  recycles object owned by the source variable by moving it
+        to internal storage of the shared pool.
+
+        Source  variable  must  own  the  object,  i.e.  be  the only place where
+        reference  to  object  is  stored.  After  call  to  this function source
+        variable becomes NULL.
+
+        pool                pool
+        obj                 source variable
+
+        NOTE: this function IS thread-safe.  It  acquires  pool  lock  during its
+              operation and can be used simultaneously from several threads.
+        ************************************************************************/
+        public static void ae_shared_pool_recycle<T>(shared_pool pool, ref T obj) where T : alglib.apobject
+        {
+            sharedpoolentry new_entry;
+            
+            /* assert that pool was seeded */
+            alglib.ap.assert(pool.seed_object!=null, "ALGLIB: shared pool is not seeded, PoolRecycle() failed");
+            
+            /* assert that pointer non-null */
+            alglib.ap.assert(obj!=null, "ALGLIB: obj in ae_shared_pool_recycle() is NULL");
+            
+            /* acquire lock */
+            ae_acquire_lock(pool.pool_lock);
+            
+            /* acquire shared pool entry (reuse one from recycled_entries or malloc new one) */
+            if( pool.recycled_entries!=null )
+            {
+                /* reuse previously allocated entry */
+                new_entry = pool.recycled_entries;
+                pool.recycled_entries = new_entry.next_entry;
+            }
+            else
+            {
+                /*
+                 * Allocate memory for new entry.
+                 *
+                 * NOTE: we release pool lock during allocation because new() may raise
+                 *       exception and we do not want our pool to be left in the locked state.
+                 */
+                ae_release_lock(pool.pool_lock);
+                new_entry = new sharedpoolentry();
+                ae_acquire_lock(pool.pool_lock);
+            }
+            
+            /* add object to the list of recycled objects */
+            new_entry.obj = obj;
+            new_entry.next_entry = pool.recycled_objects;
+            pool.recycled_objects = new_entry;
+            
+            /* release lock object */
+            ae_release_lock(pool.pool_lock);
+            
+            /* release source pointer */
+            obj = null;
+        }
+
+
+        /************************************************************************
+        This function clears internal list of  recycled  objects,  but  does  not
+        change seed object managed by the pool.
+
+        pool                pool
+
+        NOTE: this function is NOT thread-safe. It does not acquire pool lock, so
+              you should NOT call it when lock can be used by another thread.
+        ************************************************************************/
+        public static void ae_shared_pool_clear_recycled(shared_pool pool)
+        {
+            pool.recycled_objects = null;
+        }
+
+
+        /************************************************************************
+        This function allows to enumerate recycled elements of the  shared  pool.
+        It stores reference to the first recycled object in the smart pointer.
+
+        IMPORTANT:
+        * in case target variable owns non-NULL value, it is rewritten
+        * recycled object IS NOT removed from pool
+        * target variable DOES NOT become owner of the new value; you can use
+          reference to recycled object, but you do not own it.
+        * this function IS NOT thread-safe
+        * you SHOULD NOT modify shared pool during enumeration (although you  can
+          modify state of the objects retrieved from pool)
+        * in case there is no recycled objects in the pool, NULL is stored to obj
+        * in case pool is not seeded, NULL is stored to obj
+
+        pool                pool
+        obj                 reference
+        ************************************************************************/
+        public static void ae_shared_pool_first_recycled<T>(shared_pool pool, ref T obj) where T : alglib.apobject
+        {   
+            /* modify internal enumeration counter */
+            pool.enumeration_counter = pool.recycled_objects;
+            
+            /* exit on empty list */
+            if( pool.enumeration_counter==null )
+            {
+                obj = null;
+                return;
+            }
+            
+            /* assign object to smart pointer */
+            obj = (T)pool.enumeration_counter.obj;
+        }
+
+
+        /************************************************************************
+        This function allows to enumerate recycled elements of the  shared  pool.
+        It stores pointer to the next recycled object in the smart pointer.
+
+        IMPORTANT:
+        * in case target variable owns non-NULL value, it is rewritten
+        * recycled object IS NOT removed from pool
+        * target pointer DOES NOT become owner of the new value
+        * this function IS NOT thread-safe
+        * you SHOULD NOT modify shared pool during enumeration (although you  can
+          modify state of the objects retrieved from pool)
+        * in case there is no recycled objects left in the pool, NULL is stored.
+        * in case pool is not seeded, NULL is stored.
+
+        pool                pool
+        obj                 target variable
+        ************************************************************************/
+        public static void ae_shared_pool_next_recycled<T>(shared_pool pool, ref T obj) where T : alglib.apobject
+        {   
+            /* exit on end of list */
+            if( pool.enumeration_counter==null )
+            {
+                obj = null;
+                return;
+            }
+            
+            /* modify internal enumeration counter */
+            pool.enumeration_counter = pool.enumeration_counter.next_entry;
+            
+            /* exit on empty list */
+            if( pool.enumeration_counter==null )
+            {
+                obj = null;
+                return;
+            }
+            
+            /* assign object to smart pointer */
+            obj = (T)pool.enumeration_counter.obj;
+        }
+
+
+        /************************************************************************
+        This function clears internal list of recycled objects and  seed  object.
+        However, pool still can be used (after initialization with another seed).
+
+        pool                pool
+        state               ALGLIB environment state
+
+        NOTE: this function is NOT thread-safe. It does not acquire pool lock, so
+              you should NOT call it when lock can be used by another thread.
+        ************************************************************************/
+        public static void ae_shared_pool_reset(shared_pool pool)
+        {   
+            pool.seed_object = null;
+            pool.recycled_objects = null;
+            pool.enumeration_counter = null;
+        }
+    }
+}
+public partial class alglib
+{
+    public partial class smp
+    {
+        public static int cores_count = 1;
+        public static volatile int cores_to_use = 1;
+        public static bool isparallelcontext()
+        {
+            return false;
+        }
+    }
+    public class smpselftests
+    {
+        public static bool runtests()
+        {
+            return true;
+        }
+    }
+    public static void setnworkers(int nworkers)
+    {
+        alglib.smp.cores_to_use = nworkers;
+    }
+    public static int getnworkers()
+    {
+        return alglib.smp.cores_to_use;
+    }
+}
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/dataanalysis.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/dataanalysis.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/dataanalysis.cs	(revision 17158)
@@ -0,0 +1,42613 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 164
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+    
+    /*************************************************************************
+    Principal components analysis
+
+    This function builds orthogonal basis  where  first  axis  corresponds  to
+    direction with maximum variance, second axis  maximizes  variance  in  the
+    subspace orthogonal to first axis and so on.
+
+    This function builds FULL basis, i.e. returns N vectors  corresponding  to
+    ALL directions, no matter how informative. If you need  just a  few  (say,
+    10 or 50) of the most important directions, you may find it faster to  use
+    one of the reduced versions:
+    * pcatruncatedsubspace() - for subspace iteration based method
+
+    It should be noted that, unlike LDA, PCA does not use class labels.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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
+
+    OUTPUT PARAMETERS:
+        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, null);
+    }
+    
+    public static void pcabuildbasis(double[,] x, int npoints, int nvars, out int info, out double[] s2, out double[,] v, alglib.xparams _params)
+    {
+        info = 0;
+        s2 = new double[0];
+        v = new double[0,0];
+        pca.pcabuildbasis(x, npoints, nvars, ref info, ref s2, ref v, _params);
+    }
+    
+    /*************************************************************************
+    Principal components analysis
+
+    This function performs truncated PCA, i.e. returns just a few most important
+    directions.
+
+    Internally it uses iterative eigensolver which is very efficient when only
+    a minor fraction of full basis is required. Thus, if you need full  basis,
+    it is better to use pcabuildbasis() function.
+
+    It should be noted that, unlike LDA, PCA does not use class labels.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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
+        NNeeded     -   number of requested components, in [1,NVars] range;
+                        this function is efficient only for NNeeded<<NVars.
+        Eps         -   desired  precision  of  vectors  returned;  underlying
+                        solver will stop iterations as soon as absolute  error
+                        in corresponding singular values  reduces  to  roughly
+                        eps*MAX(lambda[]), with lambda[] being array of  eigen
+                        values.
+                        Zero value means that  algorithm  performs  number  of
+                        iterations  specified  by  maxits  parameter,  without
+                        paying attention to precision.
+        MaxIts      -   number of iterations performed by  subspace  iteration
+                        method. Zero value means that no  limit  on  iteration
+                        count is placed (eps-based stopping condition is used).
+
+
+    OUTPUT PARAMETERS:
+        S2          -   array[NNeeded]. Variance values corresponding
+                        to basis vectors.
+        V           -   array[NVars,NNeeded]
+                        matrix, whose columns store basis vectors.
+
+    NOTE: passing eps=0 and maxits=0 results in small eps  being  selected  as
+    stopping condition. Exact value of automatically selected eps is  version-
+    -dependent.
+
+      -- ALGLIB --
+         Copyright 10.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pcatruncatedsubspace(double[,] x, int npoints, int nvars, int nneeded, double eps, int maxits, out double[] s2, out double[,] v)
+    {
+        s2 = new double[0];
+        v = new double[0,0];
+        pca.pcatruncatedsubspace(x, npoints, nvars, nneeded, eps, maxits, ref s2, ref v, null);
+    }
+    
+    public static void pcatruncatedsubspace(double[,] x, int npoints, int nvars, int nneeded, double eps, int maxits, out double[] s2, out double[,] v, alglib.xparams _params)
+    {
+        s2 = new double[0];
+        v = new double[0,0];
+        pca.pcatruncatedsubspace(x, npoints, nvars, nneeded, eps, maxits, ref s2, ref v, _params);
+    }
+    
+    /*************************************************************************
+    Sparse truncated principal components analysis
+
+    This function performs sparse truncated PCA, i.e. returns just a few  most
+    important principal components for a sparse input X.
+
+    Internally it uses iterative eigensolver which is very efficient when only
+    a minor fraction of full basis is required.
+
+    It should be noted that, unlike LDA, PCA does not use class labels.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        X           -   sparse dataset, sparse  npoints*nvars  matrix.  It  is
+                        recommended to use CRS sparse storage format;  non-CRS
+                        input will be internally converted to CRS.
+                        Matrix contains ONLY INDEPENDENT VARIABLES,  and  must
+                        be EXACTLY npoints*nvars.
+        NPoints     -   dataset size, NPoints>=0
+        NVars       -   number of independent variables, NVars>=1
+        NNeeded     -   number of requested components, in [1,NVars] range;
+                        this function is efficient only for NNeeded<<NVars.
+        Eps         -   desired  precision  of  vectors  returned;  underlying
+                        solver will stop iterations as soon as absolute  error
+                        in corresponding singular values  reduces  to  roughly
+                        eps*MAX(lambda[]), with lambda[] being array of  eigen
+                        values.
+                        Zero value means that  algorithm  performs  number  of
+                        iterations  specified  by  maxits  parameter,  without
+                        paying attention to precision.
+        MaxIts      -   number of iterations performed by  subspace  iteration
+                        method. Zero value means that no  limit  on  iteration
+                        count is placed (eps-based stopping condition is used).
+
+
+    OUTPUT PARAMETERS:
+        S2          -   array[NNeeded]. Variance values corresponding
+                        to basis vectors.
+        V           -   array[NVars,NNeeded]
+                        matrix, whose columns store basis vectors.
+
+    NOTE: passing eps=0 and maxits=0 results in small eps  being  selected  as
+          a stopping condition. Exact value of automatically selected  eps  is
+          version-dependent.
+
+    NOTE: zero  MaxIts  is  silently  replaced  by some reasonable value which
+          prevents eternal loops (possible when inputs are degenerate and  too
+          stringent stopping criteria are specified). In  current  version  it
+          is 50+2*NVars.
+
+      -- ALGLIB --
+         Copyright 10.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void pcatruncatedsubspacesparse(sparsematrix x, int npoints, int nvars, int nneeded, double eps, int maxits, out double[] s2, out double[,] v)
+    {
+        s2 = new double[0];
+        v = new double[0,0];
+        pca.pcatruncatedsubspacesparse(x.innerobj, npoints, nvars, nneeded, eps, maxits, ref s2, ref v, null);
+    }
+    
+    public static void pcatruncatedsubspacesparse(sparsematrix x, int npoints, int nvars, int nneeded, double eps, int maxits, out double[] s2, out double[,] v, alglib.xparams _params)
+    {
+        s2 = new double[0];
+        v = new double[0,0];
+        pca.pcatruncatedsubspacesparse(x.innerobj, npoints, nvars, nneeded, eps, maxits, ref s2, ref v, _params);
+    }
+
+}
+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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        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, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        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, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Model's errors:
+        * RelCLSError   -   fraction of misclassified cases.
+        * AvgCE         -   acerage cross-entropy
+        * RMSError      -   root-mean-square error
+        * AvgError      -   average error
+        * AvgRelError   -   average relative error
+
+    NOTE 1: RelCLSError/AvgCE are zero on regression problems.
+
+    NOTE 2: on classification problems  RMSError/AvgError/AvgRelError  contain
+            errors in prediction of posterior probabilities
+    *************************************************************************/
+    public class modelerrors : alglibobject
+    {
+        //
+        // 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 modelerrors()
+        {
+            _innerobj = new mlpbase.modelerrors();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new modelerrors((mlpbase.modelerrors)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private mlpbase.modelerrors _innerobj;
+        public mlpbase.modelerrors innerobj { get { return _innerobj; } }
+        public modelerrors(mlpbase.modelerrors obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class multilayerperceptron : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public multilayerperceptron()
+        {
+            _innerobj = new mlpbase.multilayerperceptron();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new multilayerperceptron((mlpbase.multilayerperceptron)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+        s.sstart_str();
+        mlpbase.mlpserialize(s, obj.innerobj, null);
+        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, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to stream.
+    
+    Data stream generated by this function is same as  string  representation
+    generated  by  string  version  of  serializer - alphanumeric characters,
+    dots, underscores, minus signs, which are grouped into words separated by
+    spaces and CR+LF.
+    
+    We recommend you to read comments on string version of serializer to find
+    out more about serialization of AlGLIB objects.
+    *************************************************************************/
+    public static void mlpserialize(multilayerperceptron obj, System.IO.Stream stream_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        mlpbase.mlpalloc(s, obj.innerobj, null);
+        s.sstart_stream(stream_out);
+        mlpbase.mlpserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from stream.
+    *************************************************************************/
+    public static void mlpunserialize(System.IO.Stream stream_in, out multilayerperceptron obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new multilayerperceptron();
+        s.ustart_stream(stream_in);
+        mlpbase.mlpunserialize(s, obj.innerobj, null);
+        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, null);
+    }
+    
+    public static void mlpcreate0(int nin, int nout, out multilayerperceptron network, alglib.xparams _params)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreate0(nin, nout, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpcreate1(int nin, int nhid, int nout, out multilayerperceptron network, alglib.xparams _params)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreate1(nin, nhid, nout, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpcreate2(int nin, int nhid1, int nhid2, int nout, out multilayerperceptron network, alglib.xparams _params)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreate2(nin, nhid1, nhid2, nout, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpcreateb0(int nin, int nout, double b, double d, out multilayerperceptron network, alglib.xparams _params)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreateb0(nin, nout, b, d, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpcreateb1(int nin, int nhid, int nout, double b, double d, out multilayerperceptron network, alglib.xparams _params)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreateb1(nin, nhid, nout, b, d, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpcreateb2(int nin, int nhid1, int nhid2, int nout, double b, double d, out multilayerperceptron network, alglib.xparams _params)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreateb2(nin, nhid1, nhid2, nout, b, d, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpcreater0(int nin, int nout, double a, double b, out multilayerperceptron network, alglib.xparams _params)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreater0(nin, nout, a, b, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpcreater1(int nin, int nhid, int nout, double a, double b, out multilayerperceptron network, alglib.xparams _params)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreater1(nin, nhid, nout, a, b, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpcreater2(int nin, int nhid1, int nhid2, int nout, double a, double b, out multilayerperceptron network, alglib.xparams _params)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreater2(nin, nhid1, nhid2, nout, a, b, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpcreatec0(int nin, int nout, out multilayerperceptron network, alglib.xparams _params)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreatec0(nin, nout, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpcreatec1(int nin, int nhid, int nout, out multilayerperceptron network, alglib.xparams _params)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreatec1(nin, nhid, nout, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpcreatec2(int nin, int nhid1, int nhid2, int nout, out multilayerperceptron network, alglib.xparams _params)
+    {
+        network = new multilayerperceptron();
+        mlpbase.mlpcreatec2(nin, nhid1, nhid2, nout, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, out multilayerperceptron network2)
+    {
+        network2 = new multilayerperceptron();
+        mlpbase.mlpcopy(network1.innerobj, network2.innerobj, null);
+    }
+    
+    public static void mlpcopy(multilayerperceptron network1, out multilayerperceptron network2, alglib.xparams _params)
+    {
+        network2 = new multilayerperceptron();
+        mlpbase.mlpcopy(network1.innerobj, network2.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function copies tunable  parameters (weights/means/sigmas)  from  one
+    network to another with same architecture. It  performs  some  rudimentary
+    checks that architectures are same, and throws exception if check fails.
+
+    It is intended for fast copying of states between two  network  which  are
+    known to have same geometry.
+
+    INPUT PARAMETERS:
+        Network1 -   source, must be correctly initialized
+        Network2 -   target, must have same architecture
+
+    OUTPUT PARAMETERS:
+        Network2 -   network state is copied from source to target
+
+      -- ALGLIB --
+         Copyright 20.06.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcopytunableparameters(multilayerperceptron network1, multilayerperceptron network2)
+    {
+    
+        mlpbase.mlpcopytunableparameters(network1.innerobj, network2.innerobj, null);
+    }
+    
+    public static void mlpcopytunableparameters(multilayerperceptron network1, multilayerperceptron network2, alglib.xparams _params)
+    {
+    
+        mlpbase.mlpcopytunableparameters(network1.innerobj, network2.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Randomization of neural network weights
+
+      -- ALGLIB --
+         Copyright 06.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlprandomize(multilayerperceptron network)
+    {
+    
+        mlpbase.mlprandomize(network.innerobj, null);
+    }
+    
+    public static void mlprandomize(multilayerperceptron network, alglib.xparams _params)
+    {
+    
+        mlpbase.mlprandomize(network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlprandomizefull(multilayerperceptron network, alglib.xparams _params)
+    {
+    
+        mlpbase.mlprandomizefull(network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Internal subroutine.
+
+      -- ALGLIB --
+         Copyright 30.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpinitpreprocessor(multilayerperceptron network, double[,] xy, int ssize)
+    {
+    
+        mlpbase.mlpinitpreprocessor(network.innerobj, xy, ssize, null);
+    }
+    
+    public static void mlpinitpreprocessor(multilayerperceptron network, double[,] xy, int ssize, alglib.xparams _params)
+    {
+    
+        mlpbase.mlpinitpreprocessor(network.innerobj, xy, ssize, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpproperties(multilayerperceptron network, out int nin, out int nout, out int wcount, alglib.xparams _params)
+    {
+        nin = 0;
+        nout = 0;
+        wcount = 0;
+        mlpbase.mlpproperties(network.innerobj, ref nin, ref nout, ref wcount, _params);
+    }
+    
+    /*************************************************************************
+    Returns number of inputs.
+
+      -- ALGLIB --
+         Copyright 19.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static int mlpgetinputscount(multilayerperceptron network)
+    {
+    
+        return mlpbase.mlpgetinputscount(network.innerobj, null);
+    }
+    
+    public static int mlpgetinputscount(multilayerperceptron network, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpgetinputscount(network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Returns number of outputs.
+
+      -- ALGLIB --
+         Copyright 19.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static int mlpgetoutputscount(multilayerperceptron network)
+    {
+    
+        return mlpbase.mlpgetoutputscount(network.innerobj, null);
+    }
+    
+    public static int mlpgetoutputscount(multilayerperceptron network, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpgetoutputscount(network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Returns number of weights.
+
+      -- ALGLIB --
+         Copyright 19.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static int mlpgetweightscount(multilayerperceptron network)
+    {
+    
+        return mlpbase.mlpgetweightscount(network.innerobj, null);
+    }
+    
+    public static int mlpgetweightscount(multilayerperceptron network, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpgetweightscount(network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return mlpbase.mlpissoftmax(network.innerobj, null);
+    }
+    
+    public static bool mlpissoftmax(multilayerperceptron network, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpissoftmax(network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return mlpbase.mlpgetlayerscount(network.innerobj, null);
+    }
+    
+    public static int mlpgetlayerscount(multilayerperceptron network, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpgetlayerscount(network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return mlpbase.mlpgetlayersize(network.innerobj, k, null);
+    }
+    
+    public static int mlpgetlayersize(multilayerperceptron network, int k, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpgetlayersize(network.innerobj, k, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpgetinputscaling(multilayerperceptron network, int i, out double mean, out double sigma, alglib.xparams _params)
+    {
+        mean = 0;
+        sigma = 0;
+        mlpbase.mlpgetinputscaling(network.innerobj, i, ref mean, ref sigma, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpgetoutputscaling(multilayerperceptron network, int i, out double mean, out double sigma, alglib.xparams _params)
+    {
+        mean = 0;
+        sigma = 0;
+        mlpbase.mlpgetoutputscaling(network.innerobj, i, ref mean, ref sigma, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpgetneuroninfo(multilayerperceptron network, int k, int i, out int fkind, out double threshold, alglib.xparams _params)
+    {
+        fkind = 0;
+        threshold = 0;
+        mlpbase.mlpgetneuroninfo(network.innerobj, k, i, ref fkind, ref threshold, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return mlpbase.mlpgetweight(network.innerobj, k0, i0, k1, i1, null);
+    }
+    
+    public static double mlpgetweight(multilayerperceptron network, int k0, int i0, int k1, int i1, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpgetweight(network.innerobj, k0, i0, k1, i1, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpsetinputscaling(multilayerperceptron network, int i, double mean, double sigma, alglib.xparams _params)
+    {
+    
+        mlpbase.mlpsetinputscaling(network.innerobj, i, mean, sigma, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpsetoutputscaling(multilayerperceptron network, int i, double mean, double sigma, alglib.xparams _params)
+    {
+    
+        mlpbase.mlpsetoutputscaling(network.innerobj, i, mean, sigma, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpsetneuroninfo(multilayerperceptron network, int k, int i, int fkind, double threshold, alglib.xparams _params)
+    {
+    
+        mlpbase.mlpsetneuroninfo(network.innerobj, k, i, fkind, threshold, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpsetweight(multilayerperceptron network, int k0, int i0, int k1, int i1, double w, alglib.xparams _params)
+    {
+    
+        mlpbase.mlpsetweight(network.innerobj, k0, i0, k1, i1, w, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpactivationfunction(double net, int k, out double f, out double df, out double d2f, alglib.xparams _params)
+    {
+        f = 0;
+        df = 0;
+        d2f = 0;
+        mlpbase.mlpactivationfunction(net, k, ref f, ref df, ref d2f, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpprocess(multilayerperceptron network, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        mlpbase.mlpprocess(network.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    '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, null);
+    }
+    
+    public static void mlpprocessi(multilayerperceptron network, double[] x, out double[] y, alglib.xparams _params)
+    {
+        y = new double[0];
+        mlpbase.mlpprocessi(network.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    Error of the neural network on dataset.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network;
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format;
+        NPoints     -   points count.
+
+    RESULT:
+        sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2)
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlperror(multilayerperceptron network, double[,] xy, int npoints)
+    {
+    
+        return mlpbase.mlperror(network.innerobj, xy, npoints, null);
+    }
+    
+    public static double mlperror(multilayerperceptron network, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlperror(network.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Error of the neural network on dataset given by sparse matrix.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format. This function checks  correctness
+                        of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                        correct) and throws exception when  incorrect  dataset
+                        is passed.  Sparse  matrix  must  use  CRS  format for
+                        storage.
+        NPoints     -   points count, >=0
+
+    RESULT:
+        sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2)
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 23.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlperrorsparse(multilayerperceptron network, sparsematrix xy, int npoints)
+    {
+    
+        return mlpbase.mlperrorsparse(network.innerobj, xy.innerobj, npoints, null);
+    }
+    
+    public static double mlperrorsparse(multilayerperceptron network, sparsematrix xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlperrorsparse(network.innerobj, xy.innerobj, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Natural error function for neural network, internal subroutine.
+
+    NOTE: this function is single-threaded. Unlike other  error  function,  it
+    receives no speed-up from being executed in SMP mode.
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlperrorn(multilayerperceptron network, double[,] xy, int ssize)
+    {
+    
+        return mlpbase.mlperrorn(network.innerobj, xy, ssize, null);
+    }
+    
+    public static double mlperrorn(multilayerperceptron network, double[,] xy, int ssize, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlperrorn(network.innerobj, xy, ssize, _params);
+    }
+    
+    /*************************************************************************
+    Classification error of the neural network on dataset.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network;
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format;
+        NPoints     -   points count.
+
+    RESULT:
+        classification error (number of misclassified cases)
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static int mlpclserror(multilayerperceptron network, double[,] xy, int npoints)
+    {
+    
+        return mlpbase.mlpclserror(network.innerobj, xy, npoints, null);
+    }
+    
+    public static int mlpclserror(multilayerperceptron network, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpclserror(network.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Relative classification error on the test set.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network;
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format;
+        NPoints     -   points count.
+
+    RESULT:
+    Percent   of incorrectly   classified  cases.  Works  both  for classifier
+    networks and general purpose networks used as classifiers.
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 25.12.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlprelclserror(multilayerperceptron network, double[,] xy, int npoints)
+    {
+    
+        return mlpbase.mlprelclserror(network.innerobj, xy, npoints, null);
+    }
+    
+    public static double mlprelclserror(multilayerperceptron network, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlprelclserror(network.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Relative classification error on the test set given by sparse matrix.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network;
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format. Sparse matrix must use CRS format
+                        for storage.
+        NPoints     -   points count, >=0.
+
+    RESULT:
+    Percent   of incorrectly   classified  cases.  Works  both  for classifier
+    networks and general purpose networks used as classifiers.
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 09.08.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlprelclserrorsparse(multilayerperceptron network, sparsematrix xy, int npoints)
+    {
+    
+        return mlpbase.mlprelclserrorsparse(network.innerobj, xy.innerobj, npoints, null);
+    }
+    
+    public static double mlprelclserrorsparse(multilayerperceptron network, sparsematrix xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlprelclserrorsparse(network.innerobj, xy.innerobj, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Average cross-entropy  (in bits  per element) on the test set.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network;
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format;
+        NPoints     -   points count.
+
+    RESULT:
+    CrossEntropy/(NPoints*LN(2)).
+    Zero if network solves regression task.
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 08.01.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpavgce(multilayerperceptron network, double[,] xy, int npoints)
+    {
+    
+        return mlpbase.mlpavgce(network.innerobj, xy, npoints, null);
+    }
+    
+    public static double mlpavgce(multilayerperceptron network, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpavgce(network.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Average  cross-entropy  (in bits  per element)  on the  test set  given by
+    sparse matrix.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network;
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format. This function checks  correctness
+                        of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                        correct) and throws exception when  incorrect  dataset
+                        is passed.  Sparse  matrix  must  use  CRS  format for
+                        storage.
+        NPoints     -   points count, >=0.
+
+    RESULT:
+    CrossEntropy/(NPoints*LN(2)).
+    Zero if network solves regression task.
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 9.08.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpavgcesparse(multilayerperceptron network, sparsematrix xy, int npoints)
+    {
+    
+        return mlpbase.mlpavgcesparse(network.innerobj, xy.innerobj, npoints, null);
+    }
+    
+    public static double mlpavgcesparse(multilayerperceptron network, sparsematrix xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpavgcesparse(network.innerobj, xy.innerobj, npoints, _params);
+    }
+    
+    /*************************************************************************
+    RMS error on the test set given.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network;
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format;
+        NPoints     -   points count.
+
+    RESULT:
+    Root mean  square error. Its meaning for regression task is obvious. As for
+    classification  task,  RMS  error  means  error  when estimating  posterior
+    probabilities.
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 04.11.2007 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlprmserror(multilayerperceptron network, double[,] xy, int npoints)
+    {
+    
+        return mlpbase.mlprmserror(network.innerobj, xy, npoints, null);
+    }
+    
+    public static double mlprmserror(multilayerperceptron network, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlprmserror(network.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    RMS error on the test set given by sparse matrix.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network;
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format. This function checks  correctness
+                        of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                        correct) and throws exception when  incorrect  dataset
+                        is passed.  Sparse  matrix  must  use  CRS  format for
+                        storage.
+        NPoints     -   points count, >=0.
+
+    RESULT:
+    Root mean  square error. Its meaning for regression task is obvious. As for
+    classification  task,  RMS  error  means  error  when estimating  posterior
+    probabilities.
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 09.08.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlprmserrorsparse(multilayerperceptron network, sparsematrix xy, int npoints)
+    {
+    
+        return mlpbase.mlprmserrorsparse(network.innerobj, xy.innerobj, npoints, null);
+    }
+    
+    public static double mlprmserrorsparse(multilayerperceptron network, sparsematrix xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlprmserrorsparse(network.innerobj, xy.innerobj, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Average absolute error on the test set.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network;
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format;
+        NPoints     -   points count.
+
+    RESULT:
+    Its meaning for regression task is obvious. As for classification task, it
+    means average error when estimating posterior probabilities.
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 11.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpavgerror(multilayerperceptron network, double[,] xy, int npoints)
+    {
+    
+        return mlpbase.mlpavgerror(network.innerobj, xy, npoints, null);
+    }
+    
+    public static double mlpavgerror(multilayerperceptron network, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpavgerror(network.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Average absolute error on the test set given by sparse matrix.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network;
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format. This function checks  correctness
+                        of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                        correct) and throws exception when  incorrect  dataset
+                        is passed.  Sparse  matrix  must  use  CRS  format for
+                        storage.
+        NPoints     -   points count, >=0.
+
+    RESULT:
+    Its meaning for regression task is obvious. As for classification task, it
+    means average error when estimating posterior probabilities.
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 09.08.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpavgerrorsparse(multilayerperceptron network, sparsematrix xy, int npoints)
+    {
+    
+        return mlpbase.mlpavgerrorsparse(network.innerobj, xy.innerobj, npoints, null);
+    }
+    
+    public static double mlpavgerrorsparse(multilayerperceptron network, sparsematrix xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpavgerrorsparse(network.innerobj, xy.innerobj, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Average relative error on the test set.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network;
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format;
+        NPoints     -   points count.
+
+    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.
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 11.03.2008 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpavgrelerror(multilayerperceptron network, double[,] xy, int npoints)
+    {
+    
+        return mlpbase.mlpavgrelerror(network.innerobj, xy, npoints, null);
+    }
+    
+    public static double mlpavgrelerror(multilayerperceptron network, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpavgrelerror(network.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Average relative error on the test set given by sparse matrix.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network     -   neural network;
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format. This function checks  correctness
+                        of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                        correct) and throws exception when  incorrect  dataset
+                        is passed.  Sparse  matrix  must  use  CRS  format for
+                        storage.
+        NPoints     -   points count, >=0.
+
+    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.
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 09.08.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlpavgrelerrorsparse(multilayerperceptron network, sparsematrix xy, int npoints)
+    {
+    
+        return mlpbase.mlpavgrelerrorsparse(network.innerobj, xy.innerobj, npoints, null);
+    }
+    
+    public static double mlpavgrelerrorsparse(multilayerperceptron network, sparsematrix xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlpavgrelerrorsparse(network.innerobj, xy.innerobj, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpgrad(multilayerperceptron network, double[] x, double[] desiredy, out double e, ref double[] grad, alglib.xparams _params)
+    {
+        e = 0;
+        mlpbase.mlpgrad(network.innerobj, x, desiredy, ref e, ref grad, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpgradn(multilayerperceptron network, double[] x, double[] desiredy, out double e, ref double[] grad, alglib.xparams _params)
+    {
+        e = 0;
+        mlpbase.mlpgradn(network.innerobj, x, desiredy, ref e, ref grad, _params);
+    }
+    
+    /*************************************************************************
+    Batch gradient calculation for a set of inputs/outputs
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network -   network initialized with one of the network creation funcs
+        XY      -   original dataset in dense format; one sample = one row:
+                    * first NIn columns contain inputs,
+                    * for regression problem, next NOut columns store
+                      desired outputs.
+                    * for classification problem, next column (just one!)
+                      stores class number.
+        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, null);
+    }
+    
+    public static void mlpgradbatch(multilayerperceptron network, double[,] xy, int ssize, out double e, ref double[] grad, alglib.xparams _params)
+    {
+        e = 0;
+        mlpbase.mlpgradbatch(network.innerobj, xy, ssize, ref e, ref grad, _params);
+    }
+    
+    /*************************************************************************
+    Batch gradient calculation for a set  of inputs/outputs  given  by  sparse
+    matrices
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network -   network initialized with one of the network creation funcs
+        XY      -   original dataset in sparse format; one sample = one row:
+                    * MATRIX MUST BE STORED IN CRS FORMAT
+                    * first NIn columns contain inputs.
+                    * for regression problem, next NOut columns store
+                      desired outputs.
+                    * for classification problem, next column (just one!)
+                      stores class number.
+        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 26.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpgradbatchsparse(multilayerperceptron network, sparsematrix xy, int ssize, out double e, ref double[] grad)
+    {
+        e = 0;
+        mlpbase.mlpgradbatchsparse(network.innerobj, xy.innerobj, ssize, ref e, ref grad, null);
+    }
+    
+    public static void mlpgradbatchsparse(multilayerperceptron network, sparsematrix xy, int ssize, out double e, ref double[] grad, alglib.xparams _params)
+    {
+        e = 0;
+        mlpbase.mlpgradbatchsparse(network.innerobj, xy.innerobj, ssize, ref e, ref grad, _params);
+    }
+    
+    /*************************************************************************
+    Batch gradient calculation for a subset of dataset
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network -   network initialized with one of the network creation funcs
+        XY      -   original dataset in dense format; one sample = one row:
+                    * first NIn columns contain inputs,
+                    * for regression problem, next NOut columns store
+                      desired outputs.
+                    * for classification problem, next column (just one!)
+                      stores class number.
+        SetSize -   real size of XY, SetSize>=0;
+        Idx     -   subset of SubsetSize elements, array[SubsetSize]:
+                    * Idx[I] stores row index in the original dataset which is
+                      given by XY. Gradient is calculated with respect to rows
+                      whose indexes are stored in Idx[].
+                    * Idx[]  must store correct indexes; this function  throws
+                      an  exception  in  case  incorrect index (less than 0 or
+                      larger than rows(XY)) is given
+                    * Idx[]  may  store  indexes  in  any  order and even with
+                      repetitions.
+        SubsetSize- number of elements in Idx[] array:
+                    * positive value means that subset given by Idx[] is processed
+                    * zero value results in zero gradient
+                    * negative value means that full dataset is processed
+        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 26.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpgradbatchsubset(multilayerperceptron network, double[,] xy, int setsize, int[] idx, int subsetsize, out double e, ref double[] grad)
+    {
+        e = 0;
+        mlpbase.mlpgradbatchsubset(network.innerobj, xy, setsize, idx, subsetsize, ref e, ref grad, null);
+    }
+    
+    public static void mlpgradbatchsubset(multilayerperceptron network, double[,] xy, int setsize, int[] idx, int subsetsize, out double e, ref double[] grad, alglib.xparams _params)
+    {
+        e = 0;
+        mlpbase.mlpgradbatchsubset(network.innerobj, xy, setsize, idx, subsetsize, ref e, ref grad, _params);
+    }
+    
+    /*************************************************************************
+    Batch gradient calculation for a set of inputs/outputs  for  a  subset  of
+    dataset given by set of indexes.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network -   network initialized with one of the network creation funcs
+        XY      -   original dataset in sparse format; one sample = one row:
+                    * MATRIX MUST BE STORED IN CRS FORMAT
+                    * first NIn columns contain inputs,
+                    * for regression problem, next NOut columns store
+                      desired outputs.
+                    * for classification problem, next column (just one!)
+                      stores class number.
+        SetSize -   real size of XY, SetSize>=0;
+        Idx     -   subset of SubsetSize elements, array[SubsetSize]:
+                    * Idx[I] stores row index in the original dataset which is
+                      given by XY. Gradient is calculated with respect to rows
+                      whose indexes are stored in Idx[].
+                    * Idx[]  must store correct indexes; this function  throws
+                      an  exception  in  case  incorrect index (less than 0 or
+                      larger than rows(XY)) is given
+                    * Idx[]  may  store  indexes  in  any  order and even with
+                      repetitions.
+        SubsetSize- number of elements in Idx[] array:
+                    * positive value means that subset given by Idx[] is processed
+                    * zero value results in zero gradient
+                    * negative value means that full dataset is processed
+        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]
+
+    NOTE: when  SubsetSize<0 is used full dataset by call MLPGradBatchSparse
+          function.
+
+      -- ALGLIB --
+         Copyright 26.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpgradbatchsparsesubset(multilayerperceptron network, sparsematrix xy, int setsize, int[] idx, int subsetsize, out double e, ref double[] grad)
+    {
+        e = 0;
+        mlpbase.mlpgradbatchsparsesubset(network.innerobj, xy.innerobj, setsize, idx, subsetsize, ref e, ref grad, null);
+    }
+    
+    public static void mlpgradbatchsparsesubset(multilayerperceptron network, sparsematrix xy, int setsize, int[] idx, int subsetsize, out double e, ref double[] grad, alglib.xparams _params)
+    {
+        e = 0;
+        mlpbase.mlpgradbatchsparsesubset(network.innerobj, xy.innerobj, setsize, idx, subsetsize, ref e, ref grad, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpgradnbatch(multilayerperceptron network, double[,] xy, int ssize, out double e, ref double[] grad, alglib.xparams _params)
+    {
+        e = 0;
+        mlpbase.mlpgradnbatch(network.innerobj, xy, ssize, ref e, ref grad, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlphessiannbatch(multilayerperceptron network, double[,] xy, int ssize, out double e, ref double[] grad, ref double[,] h, alglib.xparams _params)
+    {
+        e = 0;
+        mlpbase.mlphessiannbatch(network.innerobj, xy, ssize, ref e, ref grad, ref h, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlphessianbatch(multilayerperceptron network, double[,] xy, int ssize, out double e, ref double[] grad, ref double[,] h, alglib.xparams _params)
+    {
+        e = 0;
+        mlpbase.mlphessianbatch(network.innerobj, xy, ssize, ref e, ref grad, ref h, _params);
+    }
+    
+    /*************************************************************************
+    Calculation of all types of errors on subset of dataset.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network -   network initialized with one of the network creation funcs
+        XY      -   original dataset; one sample = one row;
+                    first NIn columns contain inputs,
+                    next NOut columns - desired outputs.
+        SetSize -   real size of XY, SetSize>=0;
+        Subset  -   subset of SubsetSize elements, array[SubsetSize];
+        SubsetSize- number of elements in Subset[] array:
+                    * if SubsetSize>0, rows of XY with indices Subset[0]...
+                      ...Subset[SubsetSize-1] are processed
+                    * if SubsetSize=0, zeros are returned
+                    * if SubsetSize<0, entire dataset is  processed;  Subset[]
+                      array is ignored in this case.
+
+    OUTPUT PARAMETERS:
+        Rep     -   it contains all type of errors.
+
+      -- ALGLIB --
+         Copyright 04.09.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpallerrorssubset(multilayerperceptron network, double[,] xy, int setsize, int[] subset, int subsetsize, out modelerrors rep)
+    {
+        rep = new modelerrors();
+        mlpbase.mlpallerrorssubset(network.innerobj, xy, setsize, subset, subsetsize, rep.innerobj, null);
+    }
+    
+    public static void mlpallerrorssubset(multilayerperceptron network, double[,] xy, int setsize, int[] subset, int subsetsize, out modelerrors rep, alglib.xparams _params)
+    {
+        rep = new modelerrors();
+        mlpbase.mlpallerrorssubset(network.innerobj, xy, setsize, subset, subsetsize, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Calculation of all types of errors on subset of dataset.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network -   network initialized with one of the network creation funcs
+        XY      -   original dataset given by sparse matrix;
+                    one sample = one row;
+                    first NIn columns contain inputs,
+                    next NOut columns - desired outputs.
+        SetSize -   real size of XY, SetSize>=0;
+        Subset  -   subset of SubsetSize elements, array[SubsetSize];
+        SubsetSize- number of elements in Subset[] array:
+                    * if SubsetSize>0, rows of XY with indices Subset[0]...
+                      ...Subset[SubsetSize-1] are processed
+                    * if SubsetSize=0, zeros are returned
+                    * if SubsetSize<0, entire dataset is  processed;  Subset[]
+                      array is ignored in this case.
+
+    OUTPUT PARAMETERS:
+        Rep     -   it contains all type of errors.
+
+
+      -- ALGLIB --
+         Copyright 04.09.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpallerrorssparsesubset(multilayerperceptron network, sparsematrix xy, int setsize, int[] subset, int subsetsize, out modelerrors rep)
+    {
+        rep = new modelerrors();
+        mlpbase.mlpallerrorssparsesubset(network.innerobj, xy.innerobj, setsize, subset, subsetsize, rep.innerobj, null);
+    }
+    
+    public static void mlpallerrorssparsesubset(multilayerperceptron network, sparsematrix xy, int setsize, int[] subset, int subsetsize, out modelerrors rep, alglib.xparams _params)
+    {
+        rep = new modelerrors();
+        mlpbase.mlpallerrorssparsesubset(network.innerobj, xy.innerobj, setsize, subset, subsetsize, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Error of the neural network on subset of dataset.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network   -     neural network;
+        XY        -     training  set,  see  below  for  information  on   the
+                        training set format;
+        SetSize   -     real size of XY, SetSize>=0;
+        Subset    -     subset of SubsetSize elements, array[SubsetSize];
+        SubsetSize-     number of elements in Subset[] array:
+                        * if SubsetSize>0, rows of XY with indices Subset[0]...
+                          ...Subset[SubsetSize-1] are processed
+                        * if SubsetSize=0, zeros are returned
+                        * if SubsetSize<0, entire dataset is  processed;  Subset[]
+                          array is ignored in this case.
+
+    RESULT:
+        sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2)
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 04.09.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlperrorsubset(multilayerperceptron network, double[,] xy, int setsize, int[] subset, int subsetsize)
+    {
+    
+        return mlpbase.mlperrorsubset(network.innerobj, xy, setsize, subset, subsetsize, null);
+    }
+    
+    public static double mlperrorsubset(multilayerperceptron network, double[,] xy, int setsize, int[] subset, int subsetsize, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlperrorsubset(network.innerobj, xy, setsize, subset, subsetsize, _params);
+    }
+    
+    /*************************************************************************
+    Error of the neural network on subset of sparse dataset.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        Network   -     neural network;
+        XY        -     training  set,  see  below  for  information  on   the
+                        training set format. This function checks  correctness
+                        of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                        correct) and throws exception when  incorrect  dataset
+                        is passed.  Sparse  matrix  must  use  CRS  format for
+                        storage.
+        SetSize   -     real size of XY, SetSize>=0;
+                        it is used when SubsetSize<0;
+        Subset    -     subset of SubsetSize elements, array[SubsetSize];
+        SubsetSize-     number of elements in Subset[] array:
+                        * if SubsetSize>0, rows of XY with indices Subset[0]...
+                          ...Subset[SubsetSize-1] are processed
+                        * if SubsetSize=0, zeros are returned
+                        * if SubsetSize<0, entire dataset is  processed;  Subset[]
+                          array is ignored in this case.
+
+    RESULT:
+        sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2)
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    dataset format is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 04.09.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static double mlperrorsparsesubset(multilayerperceptron network, sparsematrix xy, int setsize, int[] subset, int subsetsize)
+    {
+    
+        return mlpbase.mlperrorsparsesubset(network.innerobj, xy.innerobj, setsize, subset, subsetsize, null);
+    }
+    
+    public static double mlperrorsparsesubset(multilayerperceptron network, sparsematrix xy, int setsize, int[] subset, int subsetsize, alglib.xparams _params)
+    {
+    
+        return mlpbase.mlperrorsparsesubset(network.innerobj, xy.innerobj, setsize, subset, subsetsize, _params);
+    }
+
+}
+public partial class alglib
+{
+
+    
+    /*************************************************************************
+    Multiclass Fisher LDA
+
+    Subroutine finds coefficients of linear combination which optimally separates
+    training set on classes.
+
+    COMMERCIAL EDITION OF ALGLIB:
+
+      ! Commercial version of ALGLIB includes two important  improvements   of
+      ! this function, which can be used from C++ and C#:
+      ! * Intel MKL support (lightweight Intel MKL is shipped with ALGLIB)
+      ! * multithreading support
+      !
+      ! Intel MKL gives approximately constant  (with  respect  to  number  of
+      ! worker threads) acceleration factor which depends on CPU  being  used,
+      ! problem  size  and  "baseline"  ALGLIB  edition  which  is  used   for
+      ! comparison. Best results are achieved  for  high-dimensional  problems
+      ! (NVars is at least 256).
+      !
+      ! Multithreading is used to  accelerate  initial  phase  of  LDA,  which
+      ! includes calculation of products of large matrices.  Again,  for  best
+      ! efficiency problem must be high-dimensional.
+      !
+      ! Generally, commercial ALGLIB is several times faster than  open-source
+      ! generic C edition, and many times faster than open-source C# edition.
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void fisherlda(double[,] xy, int npoints, int nvars, int nclasses, out int info, out double[] w, alglib.xparams _params)
+    {
+        info = 0;
+        w = new double[0];
+        lda.fisherlda(xy, npoints, nvars, nclasses, ref info, ref w, _params);
+    }
+    
+    /*************************************************************************
+    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).
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void fisherldan(double[,] xy, int npoints, int nvars, int nclasses, out int info, out double[,] w, alglib.xparams _params)
+    {
+        info = 0;
+        w = new double[0,0];
+        lda.fisherldan(xy, npoints, nvars, nclasses, ref info, ref w, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This object stores state of the SSA model.
+
+    You should use ALGLIB functions to work with this object.
+    *************************************************************************/
+    public class ssamodel : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public ssamodel()
+        {
+            _innerobj = new ssa.ssamodel();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new ssamodel((ssa.ssamodel)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private ssa.ssamodel _innerobj;
+        public ssa.ssamodel innerobj { get { return _innerobj; } }
+        public ssamodel(ssa.ssamodel obj)
+        {
+            _innerobj = obj;
+        }
+    }
+    
+    /*************************************************************************
+    This function creates SSA model object.  Right after creation model is  in
+    "dummy" mode - you can add data,  but   analyzing/prediction  will  return
+    just zeros (it assumes that basis is empty).
+
+    HOW TO USE SSA MODEL:
+
+    1. create model with ssacreate()
+    2. add data with one/many ssaaddsequence() calls
+    3. choose SSA algorithm with one of ssasetalgo...() functions:
+       * ssasetalgotopkdirect() for direct one-run analysis
+       * ssasetalgotopkrealtime() for algorithm optimized for many  subsequent
+         runs with warm-start capabilities
+       * ssasetalgoprecomputed() for user-supplied basis
+    4. set window width with ssasetwindow()
+    5. perform one of the analysis-related activities:
+       a) call ssagetbasis() to get basis
+       b) call ssaanalyzelast() ssaanalyzesequence() or ssaanalyzelastwindow()
+          to perform analysis (trend/noise separation)
+       c) call  one  of   the   forecasting   functions  (ssaforecastlast() or
+          ssaforecastsequence()) to perform prediction; alternatively, you can
+          extract linear recurrence coefficients with ssagetlrr().
+       SSA analysis will be performed during first  call  to  analysis-related
+       function. SSA model is smart enough to track all changes in the dataset
+       and  model  settings,  to  cache  previously  computed  basis  and   to
+       re-evaluate basis only when necessary.
+
+    Additionally, if your setting involves constant stream  of  incoming data,
+    you can perform quick update already calculated  model  with  one  of  the
+    incremental   append-and-update   functions:  ssaappendpointandupdate() or
+    ssaappendsequenceandupdate().
+
+    NOTE: steps (2), (3), (4) can be performed in arbitrary order.
+
+    INPUT PARAMETERS:
+        none
+
+    OUTPUT PARAMETERS:
+        S               -   structure which stores model state
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssacreate(out ssamodel s)
+    {
+        s = new ssamodel();
+        ssa.ssacreate(s.innerobj, null);
+    }
+    
+    public static void ssacreate(out ssamodel s, alglib.xparams _params)
+    {
+        s = new ssamodel();
+        ssa.ssacreate(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function sets window width for SSA model. You should call  it  before
+    analysis phase. Default window width is 1 (not for real use).
+
+    Special notes:
+    * this function call can be performed at any moment before  first call  to
+      analysis-related functions
+    * changing window width invalidates internally stored basis; if you change
+      window width AFTER you call analysis-related  function,  next  analysis
+      phase will require re-calculation of  the  basis  according  to  current
+      algorithm.
+    * calling this function with exactly  same window width as current one has
+      no effect
+    * if you specify window width larger  than any data sequence stored in the
+      model, analysis will return zero basis.
+
+    INPUT PARAMETERS:
+        S               -   SSA model created with ssacreate()
+        WindowWidth     -   >=1, new window width
+
+    OUTPUT PARAMETERS:
+        S               -   SSA model, updated
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssasetwindow(ssamodel s, int windowwidth)
+    {
+    
+        ssa.ssasetwindow(s.innerobj, windowwidth, null);
+    }
+    
+    public static void ssasetwindow(ssamodel s, int windowwidth, alglib.xparams _params)
+    {
+    
+        ssa.ssasetwindow(s.innerobj, windowwidth, _params);
+    }
+    
+    /*************************************************************************
+    This  function  sets  seed  which  is used to initialize internal RNG when
+    we make pseudorandom decisions on model updates.
+
+    By default, deterministic seed is used - which results in same sequence of
+    pseudorandom decisions every time you run SSA model. If you  specify  non-
+    deterministic seed value, then SSA  model  may  return  slightly different
+    results after each run.
+
+    This function can be useful when you have several SSA models updated  with
+    sseappendpointandupdate() called with 0<UpdateIts<1 (fractional value) and
+    due to performance limitations want them to perform updates  at  different
+    moments.
+
+    INPUT PARAMETERS:
+        S       -   SSA model
+        Seed    -   seed:
+                    * positive values = use deterministic seed for each run of
+                      algorithms which depend on random initialization
+                    * zero or negative values = use non-deterministic seed
+
+      -- ALGLIB --
+         Copyright 03.11.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssasetseed(ssamodel s, int seed)
+    {
+    
+        ssa.ssasetseed(s.innerobj, seed, null);
+    }
+    
+    public static void ssasetseed(ssamodel s, int seed, alglib.xparams _params)
+    {
+    
+        ssa.ssasetseed(s.innerobj, seed, _params);
+    }
+    
+    /*************************************************************************
+    This function sets length of power-up cycle for real-time algorithm.
+
+    By default, this algorithm performs costly O(N*WindowWidth^2)  init  phase
+    followed by full run of truncated  EVD.  However,  if  you  are  ready  to
+    live with a bit lower-quality basis during first few iterations,  you  can
+    split this O(N*WindowWidth^2) initialization  between  several  subsequent
+    append-and-update rounds. It results in better latency of the algorithm.
+
+    This function invalidates basis/solver, next analysis call will result  in
+    full recalculation of everything.
+
+    INPUT PARAMETERS:
+        S       -   SSA model
+        PWLen   -   length of the power-up stage:
+                    * 0 means that no power-up is requested
+                    * 1 is the same as 0
+                    * >1 means that delayed power-up is performed
+
+      -- ALGLIB --
+         Copyright 03.11.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssasetpoweruplength(ssamodel s, int pwlen)
+    {
+    
+        ssa.ssasetpoweruplength(s.innerobj, pwlen, null);
+    }
+    
+    public static void ssasetpoweruplength(ssamodel s, int pwlen, alglib.xparams _params)
+    {
+    
+        ssa.ssasetpoweruplength(s.innerobj, pwlen, _params);
+    }
+    
+    /*************************************************************************
+    This function sets memory limit of SSA analysis.
+
+    Straightforward SSA with sequence length T and window width W needs O(T*W)
+    memory. It is possible to reduce memory consumption by splitting task into
+    smaller chunks.
+
+    Thus function allows you to specify approximate memory limit (measured  in
+    double precision numbers used for buffers). Actual memory consumption will
+    be comparable to the number specified by you.
+
+    Default memory limit is 50.000.000 (400Mbytes) in current version.
+
+    INPUT PARAMETERS:
+        S       -   SSA model
+        MemLimit-   memory limit, >=0. Zero value means no limit.
+
+      -- ALGLIB --
+         Copyright 20.12.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssasetmemorylimit(ssamodel s, int memlimit)
+    {
+    
+        ssa.ssasetmemorylimit(s.innerobj, memlimit, null);
+    }
+    
+    public static void ssasetmemorylimit(ssamodel s, int memlimit, alglib.xparams _params)
+    {
+    
+        ssa.ssasetmemorylimit(s.innerobj, memlimit, _params);
+    }
+    
+    /*************************************************************************
+    This function adds data sequence to SSA  model.  Only   single-dimensional
+    sequences are supported.
+
+    What is a sequences? Following definitions/requirements apply:
+    * a sequence  is  an  array of  values  measured  in  subsequent,  equally
+      separated time moments (ticks).
+    * you may have many sequences  in your  dataset;  say,  one  sequence  may
+      correspond to one trading session.
+    * sequence length should be larger  than current  window  length  (shorter
+      sequences will be ignored during analysis).
+    * analysis is performed within a  sequence; different  sequences  are  NOT
+      stacked together to produce one large contiguous stream of data.
+    * analysis is performed for all  sequences at once, i.e. same set of basis
+      vectors is computed for all sequences
+
+    INCREMENTAL ANALYSIS
+
+    This function is non intended for  incremental updates of previously found
+    SSA basis. Calling it invalidates  all previous analysis results (basis is
+    reset and will be recalculated from zero during next analysis).
+
+    If  you  want  to  perform   incremental/real-time  SSA,  consider   using
+    following functions:
+    * ssaappendpointandupdate() for appending one point
+    * ssaappendsequenceandupdate() for appending new sequence
+
+    INPUT PARAMETERS:
+        S               -   SSA model created with ssacreate()
+        X               -   array[N], data, can be larger (additional values
+                            are ignored)
+        N               -   data length, can be automatically determined from
+                            the array length. N>=0.
+
+    OUTPUT PARAMETERS:
+        S               -   SSA model, updated
+
+    NOTE: you can clear dataset with ssacleardata()
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssaaddsequence(ssamodel s, double[] x, int n)
+    {
+    
+        ssa.ssaaddsequence(s.innerobj, x, n, null);
+    }
+    
+    public static void ssaaddsequence(ssamodel s, double[] x, int n, alglib.xparams _params)
+    {
+    
+        ssa.ssaaddsequence(s.innerobj, x, n, _params);
+    }
+            
+    public static void ssaaddsequence(ssamodel s, double[] x)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        ssa.ssaaddsequence(s.innerobj, x, n, null);
+    
+        return;
+    }
+            
+    public static void ssaaddsequence(ssamodel s, double[] x, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        ssa.ssaaddsequence(s.innerobj, x, n, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function appends single point to last data sequence stored in the SSA
+    model and tries to update model in the  incremental  manner  (if  possible
+    with current algorithm).
+
+    If you want to add more than one point at once:
+    * if you want to add M points to the same sequence, perform M-1 calls with
+      UpdateIts parameter set to 0.0, and last call with non-zero UpdateIts.
+    * if you want to add new sequence, use ssaappendsequenceandupdate()
+
+    Running time of this function does NOT depend on  dataset  size,  only  on
+    window width and number of singular vectors. Depending on algorithm  being
+    used, incremental update has complexity:
+    * for top-K real time   - O(UpdateIts*K*Width^2), with fractional UpdateIts
+    * for top-K direct      - O(Width^3) for any non-zero UpdateIts
+    * for precomputed basis - O(1), no update is performed
+
+    INPUT PARAMETERS:
+        S               -   SSA model created with ssacreate()
+        X               -   new point
+        UpdateIts       -   >=0,  floating  point (!)  value,  desired  update
+                            frequency:
+                            * zero value means that point is  stored,  but  no
+                              update is performed
+                            * integer part of the value means  that  specified
+                              number of iterations is always performed
+                            * fractional part of  the  value  means  that  one
+                              iteration is performed with this probability.
+
+                            Recommended value: 0<UpdateIts<=1.  Values  larger
+                            than 1 are VERY seldom  needed.  If  your  dataset
+                            changes slowly, you can set it  to  0.1  and  skip
+                            90% of updates.
+
+                            In any case, no information is lost even with zero
+                            value of UpdateIts! It will be  incorporated  into
+                            model, sooner or later.
+
+    OUTPUT PARAMETERS:
+        S               -   SSA model, updated
+
+    NOTE: this function uses internal  RNG  to  handle  fractional  values  of
+          UpdateIts. By default it  is  initialized  with  fixed  seed  during
+          initial calculation of basis. Thus subsequent calls to this function
+          will result in the same sequence of pseudorandom decisions.
+
+          However, if  you  have  several  SSA  models  which  are  calculated
+          simultaneously, and if you want to reduce computational  bottlenecks
+          by performing random updates at random moments, then fixed  seed  is
+          not an option - all updates will fire at same moments.
+
+          You may change it with ssasetseed() function.
+
+    NOTE: this function throws an exception if called for empty dataset (there
+          is no "last" sequence to modify).
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssaappendpointandupdate(ssamodel s, double x, double updateits)
+    {
+    
+        ssa.ssaappendpointandupdate(s.innerobj, x, updateits, null);
+    }
+    
+    public static void ssaappendpointandupdate(ssamodel s, double x, double updateits, alglib.xparams _params)
+    {
+    
+        ssa.ssaappendpointandupdate(s.innerobj, x, updateits, _params);
+    }
+    
+    /*************************************************************************
+    This function appends new sequence to dataset stored in the SSA  model and
+    tries to update model in the incremental manner (if possible  with current
+    algorithm).
+
+    Notes:
+    * if you want to add M sequences at once, perform M-1 calls with UpdateIts
+      parameter set to 0.0, and last call with non-zero UpdateIts.
+    * if you want to add just one point, use ssaappendpointandupdate()
+
+    Running time of this function does NOT depend on  dataset  size,  only  on
+    sequence length, window width and number of singular vectors. Depending on
+    algorithm being used, incremental update has complexity:
+    * for top-K real time   - O(UpdateIts*K*Width^2+(NTicks-Width)*Width^2)
+    * for top-K direct      - O(Width^3+(NTicks-Width)*Width^2)
+    * for precomputed basis - O(1), no update is performed
+
+    INPUT PARAMETERS:
+        S               -   SSA model created with ssacreate()
+        X               -   new sequence, array[NTicks] or larget
+        NTicks          -   >=1, number of ticks in the sequence
+        UpdateIts       -   >=0,  floating  point (!)  value,  desired  update
+                            frequency:
+                            * zero value means that point is  stored,  but  no
+                              update is performed
+                            * integer part of the value means  that  specified
+                              number of iterations is always performed
+                            * fractional part of  the  value  means  that  one
+                              iteration is performed with this probability.
+
+                            Recommended value: 0<UpdateIts<=1.  Values  larger
+                            than 1 are VERY seldom  needed.  If  your  dataset
+                            changes slowly, you can set it  to  0.1  and  skip
+                            90% of updates.
+
+                            In any case, no information is lost even with zero
+                            value of UpdateIts! It will be  incorporated  into
+                            model, sooner or later.
+
+    OUTPUT PARAMETERS:
+        S               -   SSA model, updated
+
+    NOTE: this function uses internal  RNG  to  handle  fractional  values  of
+          UpdateIts. By default it  is  initialized  with  fixed  seed  during
+          initial calculation of basis. Thus subsequent calls to this function
+          will result in the same sequence of pseudorandom decisions.
+
+          However, if  you  have  several  SSA  models  which  are  calculated
+          simultaneously, and if you want to reduce computational  bottlenecks
+          by performing random updates at random moments, then fixed  seed  is
+          not an option - all updates will fire at same moments.
+
+          You may change it with ssasetseed() function.
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssaappendsequenceandupdate(ssamodel s, double[] x, int nticks, double updateits)
+    {
+    
+        ssa.ssaappendsequenceandupdate(s.innerobj, x, nticks, updateits, null);
+    }
+    
+    public static void ssaappendsequenceandupdate(ssamodel s, double[] x, int nticks, double updateits, alglib.xparams _params)
+    {
+    
+        ssa.ssaappendsequenceandupdate(s.innerobj, x, nticks, updateits, _params);
+    }
+            
+    public static void ssaappendsequenceandupdate(ssamodel s, double[] x, double updateits)
+    {
+        int nticks;
+    
+    
+        nticks = ap.len(x);
+        ssa.ssaappendsequenceandupdate(s.innerobj, x, nticks, updateits, null);
+    
+        return;
+    }
+            
+    public static void ssaappendsequenceandupdate(ssamodel s, double[] x, double updateits, alglib.xparams _params)
+    {
+        int nticks;
+    
+    
+        nticks = ap.len(x);
+        ssa.ssaappendsequenceandupdate(s.innerobj, x, nticks, updateits, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This  function sets SSA algorithm to "precomputed vectors" algorithm.
+
+    This  algorithm  uses  precomputed  set  of  orthonormal  (orthogonal  AND
+    normalized) basis vectors supplied by user. Thus, basis calculation  phase
+    is not performed -  we  already  have  our  basis  -  and  only  analysis/
+    forecasting phase requires actual calculations.
+
+    This algorithm may handle "append" requests which add just  one/few  ticks
+    to the end of the last sequence in O(1) time.
+
+    NOTE: this algorithm accepts both basis and window  width,  because  these
+          two parameters are naturally aligned.  Calling  this  function  sets
+          window width; if you call ssasetwindow() with  other  window  width,
+          then during analysis stage algorithm will detect conflict and  reset
+          to zero basis.
+
+    INPUT PARAMETERS:
+        S               -   SSA model
+        A               -   array[WindowWidth,NBasis], orthonormalized  basis;
+                            this function does NOT control  orthogonality  and
+                            does NOT perform any kind of  renormalization.  It
+                            is your responsibility to provide it with  correct
+                            basis.
+        WindowWidth     -   window width, >=1
+        NBasis          -   number of basis vectors, 1<=NBasis<=WindowWidth
+
+    OUTPUT PARAMETERS:
+        S               -   updated model
+
+    NOTE: calling this function invalidates basis in all cases.
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssasetalgoprecomputed(ssamodel s, double[,] a, int windowwidth, int nbasis)
+    {
+    
+        ssa.ssasetalgoprecomputed(s.innerobj, a, windowwidth, nbasis, null);
+    }
+    
+    public static void ssasetalgoprecomputed(ssamodel s, double[,] a, int windowwidth, int nbasis, alglib.xparams _params)
+    {
+    
+        ssa.ssasetalgoprecomputed(s.innerobj, a, windowwidth, nbasis, _params);
+    }
+            
+    public static void ssasetalgoprecomputed(ssamodel s, double[,] a)
+    {
+        int windowwidth;
+        int nbasis;
+    
+    
+        windowwidth = ap.rows(a);
+        nbasis = ap.cols(a);
+        ssa.ssasetalgoprecomputed(s.innerobj, a, windowwidth, nbasis, null);
+    
+        return;
+    }
+            
+    public static void ssasetalgoprecomputed(ssamodel s, double[,] a, alglib.xparams _params)
+    {
+        int windowwidth;
+        int nbasis;
+    
+    
+        windowwidth = ap.rows(a);
+        nbasis = ap.cols(a);
+        ssa.ssasetalgoprecomputed(s.innerobj, a, windowwidth, nbasis, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This  function sets SSA algorithm to "direct top-K" algorithm.
+
+    "Direct top-K" algorithm performs full  SVD  of  the  N*WINDOW  trajectory
+    matrix (hence its name - direct solver  is  used),  then  extracts  top  K
+    components. Overall running time is O(N*WINDOW^2), where N is a number  of
+    ticks in the dataset, WINDOW is window width.
+
+    This algorithm may handle "append" requests which add just  one/few  ticks
+    to the end of the last sequence in O(WINDOW^3) time,  which  is  ~N/WINDOW
+    times faster than re-computing everything from scratch.
+
+    INPUT PARAMETERS:
+        S               -   SSA model
+        TopK            -   number of components to analyze; TopK>=1.
+
+    OUTPUT PARAMETERS:
+        S               -   updated model
+
+
+    NOTE: TopK>WindowWidth is silently decreased to WindowWidth during analysis
+          phase
+
+    NOTE: calling this function invalidates basis, except  for  the  situation
+          when this algorithm was already set with same parameters.
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssasetalgotopkdirect(ssamodel s, int topk)
+    {
+    
+        ssa.ssasetalgotopkdirect(s.innerobj, topk, null);
+    }
+    
+    public static void ssasetalgotopkdirect(ssamodel s, int topk, alglib.xparams _params)
+    {
+    
+        ssa.ssasetalgotopkdirect(s.innerobj, topk, _params);
+    }
+    
+    /*************************************************************************
+    This function sets SSA algorithm to "top-K real time algorithm". This algo
+    extracts K components with largest singular values.
+
+    It  is  real-time  version  of  top-K  algorithm  which  is  optimized for
+    incremental processing and  fast  start-up. Internally  it  uses  subspace
+    eigensolver for truncated SVD. It results  in  ability  to  perform  quick
+    updates of the basis when only a few points/sequences is added to dataset.
+
+    Performance profile of the algorithm is given below:
+    * O(K*WindowWidth^2) running time for incremental update  of  the  dataset
+      with one of the "append-and-update" functions (ssaappendpointandupdate()
+      or ssaappendsequenceandupdate()).
+    * O(N*WindowWidth^2) running time for initial basis evaluation (N=size  of
+      dataset)
+    * ability  to  split  costly  initialization  across  several  incremental
+      updates of the basis (so called "Power-Up" functionality,  activated  by
+      ssasetpoweruplength() function)
+
+    INPUT PARAMETERS:
+        S               -   SSA model
+        TopK            -   number of components to analyze; TopK>=1.
+
+    OUTPUT PARAMETERS:
+        S               -   updated model
+
+    NOTE: this  algorithm  is  optimized  for  large-scale  tasks  with  large
+          datasets. On toy problems with just  5-10 points it can return basis
+          which is slightly different from that returned by  direct  algorithm
+          (ssasetalgotopkdirect() function). However, the  difference  becomes
+          negligible as dataset grows.
+
+    NOTE: TopK>WindowWidth is silently decreased to WindowWidth during analysis
+          phase
+
+    NOTE: calling this function invalidates basis, except  for  the  situation
+          when this algorithm was already set with same parameters.
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssasetalgotopkrealtime(ssamodel s, int topk)
+    {
+    
+        ssa.ssasetalgotopkrealtime(s.innerobj, topk, null);
+    }
+    
+    public static void ssasetalgotopkrealtime(ssamodel s, int topk, alglib.xparams _params)
+    {
+    
+        ssa.ssasetalgotopkrealtime(s.innerobj, topk, _params);
+    }
+    
+    /*************************************************************************
+    This function clears all data stored in the  model  and  invalidates  all
+    basis components found so far.
+
+    INPUT PARAMETERS:
+        S               -   SSA model created with ssacreate()
+
+    OUTPUT PARAMETERS:
+        S               -   SSA model, updated
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssacleardata(ssamodel s)
+    {
+    
+        ssa.ssacleardata(s.innerobj, null);
+    }
+    
+    public static void ssacleardata(ssamodel s, alglib.xparams _params)
+    {
+    
+        ssa.ssacleardata(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function executes SSA on internally stored dataset and returns  basis
+    found by current method.
+
+    INPUT PARAMETERS:
+        S               -   SSA model
+
+    OUTPUT PARAMETERS:
+        A               -   array[WindowWidth,NBasis],   basis;  vectors  are
+                            stored in matrix columns, by descreasing variance
+        SV              -   array[NBasis]:
+                            * zeros - for model initialized with SSASetAlgoPrecomputed()
+                            * singular values - for other algorithms
+        WindowWidth     -   current window
+        NBasis          -   basis size
+
+
+    CACHING/REUSE OF THE BASIS
+
+    Caching/reuse of previous results is performed:
+    * first call performs full run of SSA; basis is stored in the cache
+    * subsequent calls reuse previously cached basis
+    * if you call any function which changes model properties (window  length,
+      algorithm, dataset), internal basis will be invalidated.
+    * the only calls which do NOT invalidate basis are listed below:
+      a) ssasetwindow() with same window length
+      b) ssaappendpointandupdate()
+      c) ssaappendsequenceandupdate()
+      d) ssasetalgotopk...() with exactly same K
+      Calling these functions will result in reuse of previously found basis.
+
+
+    HANDLING OF DEGENERATE CASES
+
+    Calling  this  function  in  degenerate  cases  (no  data  or all data are
+    shorter than window size; no algorithm is specified)  returns  basis  with
+    just one zero vector.
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssagetbasis(ssamodel s, out double[,] a, out double[] sv, out int windowwidth, out int nbasis)
+    {
+        a = new double[0,0];
+        sv = new double[0];
+        windowwidth = 0;
+        nbasis = 0;
+        ssa.ssagetbasis(s.innerobj, ref a, ref sv, ref windowwidth, ref nbasis, null);
+    }
+    
+    public static void ssagetbasis(ssamodel s, out double[,] a, out double[] sv, out int windowwidth, out int nbasis, alglib.xparams _params)
+    {
+        a = new double[0,0];
+        sv = new double[0];
+        windowwidth = 0;
+        nbasis = 0;
+        ssa.ssagetbasis(s.innerobj, ref a, ref sv, ref windowwidth, ref nbasis, _params);
+    }
+    
+    /*************************************************************************
+    This function returns linear recurrence relation (LRR) coefficients  found
+    by current SSA algorithm.
+
+    INPUT PARAMETERS:
+        S               -   SSA model
+
+    OUTPUT PARAMETERS:
+        A               -   array[WindowWidth-1]. Coefficients  of  the
+                            linear recurrence of the form:
+                            X[W-1] = X[W-2]*A[W-2] + X[W-3]*A[W-3] + ... + X[0]*A[0].
+                            Empty array for WindowWidth=1.
+        WindowWidth     -   current window width
+
+
+    CACHING/REUSE OF THE BASIS
+
+    Caching/reuse of previous results is performed:
+    * first call performs full run of SSA; basis is stored in the cache
+    * subsequent calls reuse previously cached basis
+    * if you call any function which changes model properties (window  length,
+      algorithm, dataset), internal basis will be invalidated.
+    * the only calls which do NOT invalidate basis are listed below:
+      a) ssasetwindow() with same window length
+      b) ssaappendpointandupdate()
+      c) ssaappendsequenceandupdate()
+      d) ssasetalgotopk...() with exactly same K
+      Calling these functions will result in reuse of previously found basis.
+
+
+    HANDLING OF DEGENERATE CASES
+
+    Calling  this  function  in  degenerate  cases  (no  data  or all data are
+    shorter than window size; no algorithm is specified) returns zeros.
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssagetlrr(ssamodel s, out double[] a, out int windowwidth)
+    {
+        a = new double[0];
+        windowwidth = 0;
+        ssa.ssagetlrr(s.innerobj, ref a, ref windowwidth, null);
+    }
+    
+    public static void ssagetlrr(ssamodel s, out double[] a, out int windowwidth, alglib.xparams _params)
+    {
+        a = new double[0];
+        windowwidth = 0;
+        ssa.ssagetlrr(s.innerobj, ref a, ref windowwidth, _params);
+    }
+    
+    /*************************************************************************
+    This  function  executes  SSA  on  internally  stored  dataset and returns
+    analysis  for  the  last  window  of  the  last sequence. Such analysis is
+    an lightweight alternative for full scale reconstruction (see below).
+
+    Typical use case for this function is  real-time  setting,  when  you  are
+    interested in quick-and-dirty (very quick and very  dirty)  processing  of
+    just a few last ticks of the trend.
+
+    IMPORTANT: full  scale  SSA  involves  analysis  of  the  ENTIRE  dataset,
+               with reconstruction being done for  all  positions  of  sliding
+               window with subsequent hankelization  (diagonal  averaging)  of
+               the resulting matrix.
+
+               Such analysis requires O((DataLen-Window)*Window*NBasis)  FLOPs
+               and can be quite costly. However, it has  nice  noise-canceling
+               effects due to averaging.
+
+               This function performs REDUCED analysis of the last window.  It
+               is much faster - just O(Window*NBasis),  but  its  results  are
+               DIFFERENT from that of ssaanalyzelast(). In  particular,  first
+               few points of the trend are much more prone to noise.
+
+    INPUT PARAMETERS:
+        S               -   SSA model
+
+    OUTPUT PARAMETERS:
+        Trend           -   array[WindowSize], reconstructed trend line
+        Noise           -   array[WindowSize], the rest of the signal;
+                            it holds that ActualData = Trend+Noise.
+        NTicks          -   current WindowSize
+
+
+    CACHING/REUSE OF THE BASIS
+
+    Caching/reuse of previous results is performed:
+    * first call performs full run of SSA; basis is stored in the cache
+    * subsequent calls reuse previously cached basis
+    * if you call any function which changes model properties (window  length,
+      algorithm, dataset), internal basis will be invalidated.
+    * the only calls which do NOT invalidate basis are listed below:
+      a) ssasetwindow() with same window length
+      b) ssaappendpointandupdate()
+      c) ssaappendsequenceandupdate()
+      d) ssasetalgotopk...() with exactly same K
+      Calling these functions will result in reuse of previously found basis.
+
+    In  any  case,  only  basis  is  reused. Reconstruction is performed  from
+    scratch every time you call this function.
+
+
+    HANDLING OF DEGENERATE CASES
+
+    Following degenerate cases may happen:
+    * dataset is empty (no analysis can be done)
+    * all sequences are shorter than the window length,no analysis can be done
+    * no algorithm is specified (no analysis can be done)
+    * last sequence is shorter than the window length (analysis can  be  done,
+      but we can not perform reconstruction on the last sequence)
+
+    Calling this function in degenerate cases returns following result:
+    * in any case, WindowWidth ticks is returned
+    * trend is assumed to be zero
+    * noise is initialized by the last sequence; if last sequence  is  shorter
+      than the window size, it is moved to  the  end  of  the  array, and  the
+      beginning of the noise array is filled by zeros
+
+    No analysis is performed in degenerate cases (we immediately return  dummy
+    values, no basis is constructed).
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssaanalyzelastwindow(ssamodel s, out double[] trend, out double[] noise, out int nticks)
+    {
+        trend = new double[0];
+        noise = new double[0];
+        nticks = 0;
+        ssa.ssaanalyzelastwindow(s.innerobj, ref trend, ref noise, ref nticks, null);
+    }
+    
+    public static void ssaanalyzelastwindow(ssamodel s, out double[] trend, out double[] noise, out int nticks, alglib.xparams _params)
+    {
+        trend = new double[0];
+        noise = new double[0];
+        nticks = 0;
+        ssa.ssaanalyzelastwindow(s.innerobj, ref trend, ref noise, ref nticks, _params);
+    }
+    
+    /*************************************************************************
+    This function:
+    * builds SSA basis using internally stored (entire) dataset
+    * returns reconstruction for the last NTicks of the last sequence
+
+    If you want to analyze some other sequence, use ssaanalyzesequence().
+
+    Reconstruction phase involves  generation  of  NTicks-WindowWidth  sliding
+    windows, their decomposition using empirical orthogonal functions found by
+    SSA, followed by averaging of each data point across  several  overlapping
+    windows. Thus, every point in the output trend is reconstructed  using  up
+    to WindowWidth overlapping  windows  (WindowWidth windows exactly  in  the
+    inner points, just one window at the extremal points).
+
+    IMPORTANT: due to averaging this function returns  different  results  for
+               different values of NTicks. It is expected and not a bug.
+
+               For example:
+               * Trend[NTicks-1] is always same because it is not averaged  in
+                 any case (same applies to Trend[0]).
+               * Trend[NTicks-2] has different values  for  NTicks=WindowWidth
+                 and NTicks=WindowWidth+1 because former  case  means that  no
+                 averaging is performed, and latter  case means that averaging
+                 using two sliding windows  is  performed.  Larger  values  of
+                 NTicks produce same results as NTicks=WindowWidth+1.
+               * ...and so on...
+
+    PERFORMANCE: this  function has O((NTicks-WindowWidth)*WindowWidth*NBasis)
+                 running time. If you work  in  time-constrained  setting  and
+                 have to analyze just a few last ticks, choosing NTicks  equal
+                 to WindowWidth+SmoothingLen, with SmoothingLen=1...WindowWidth
+                 will result in good compromise between noise cancellation and
+                 analysis speed.
+
+    INPUT PARAMETERS:
+        S               -   SSA model
+        NTicks          -   number of ticks to analyze, Nticks>=1.
+                            * special case of NTicks<=WindowWidth  is  handled
+                              by analyzing last window and  returning   NTicks
+                              last ticks.
+                            * special case NTicks>LastSequenceLen  is  handled
+                              by prepending result with NTicks-LastSequenceLen
+                              zeros.
+
+    OUTPUT PARAMETERS:
+        Trend           -   array[NTicks], reconstructed trend line
+        Noise           -   array[NTicks], the rest of the signal;
+                            it holds that ActualData = Trend+Noise.
+
+
+    CACHING/REUSE OF THE BASIS
+
+    Caching/reuse of previous results is performed:
+    * first call performs full run of SSA; basis is stored in the cache
+    * subsequent calls reuse previously cached basis
+    * if you call any function which changes model properties (window  length,
+      algorithm, dataset), internal basis will be invalidated.
+    * the only calls which do NOT invalidate basis are listed below:
+      a) ssasetwindow() with same window length
+      b) ssaappendpointandupdate()
+      c) ssaappendsequenceandupdate()
+      d) ssasetalgotopk...() with exactly same K
+      Calling these functions will result in reuse of previously found basis.
+
+    In  any  case,  only  basis  is  reused. Reconstruction is performed  from
+    scratch every time you call this function.
+
+
+    HANDLING OF DEGENERATE CASES
+
+    Following degenerate cases may happen:
+    * dataset is empty (no analysis can be done)
+    * all sequences are shorter than the window length,no analysis can be done
+    * no algorithm is specified (no analysis can be done)
+    * last sequence is shorter than the window length (analysis  can  be done,
+      but we can not perform reconstruction on the last sequence)
+
+    Calling this function in degenerate cases returns following result:
+    * in any case, NTicks ticks is returned
+    * trend is assumed to be zero
+    * noise is initialized by the last sequence; if last sequence  is  shorter
+      than the window size, it is moved to  the  end  of  the  array, and  the
+      beginning of the noise array is filled by zeros
+
+    No analysis is performed in degenerate cases (we immediately return  dummy
+    values, no basis is constructed).
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssaanalyzelast(ssamodel s, int nticks, out double[] trend, out double[] noise)
+    {
+        trend = new double[0];
+        noise = new double[0];
+        ssa.ssaanalyzelast(s.innerobj, nticks, ref trend, ref noise, null);
+    }
+    
+    public static void ssaanalyzelast(ssamodel s, int nticks, out double[] trend, out double[] noise, alglib.xparams _params)
+    {
+        trend = new double[0];
+        noise = new double[0];
+        ssa.ssaanalyzelast(s.innerobj, nticks, ref trend, ref noise, _params);
+    }
+    
+    /*************************************************************************
+    This function:
+    * builds SSA basis using internally stored (entire) dataset
+    * returns reconstruction for the sequence being passed to this function
+
+    If  you  want  to  analyze  last  sequence  stored  in   the   model,  use
+    ssaanalyzelast().
+
+    Reconstruction phase involves  generation  of  NTicks-WindowWidth  sliding
+    windows, their decomposition using empirical orthogonal functions found by
+    SSA, followed by averaging of each data point across  several  overlapping
+    windows. Thus, every point in the output trend is reconstructed  using  up
+    to WindowWidth overlapping  windows  (WindowWidth windows exactly  in  the
+    inner points, just one window at the extremal points).
+
+    PERFORMANCE: this  function has O((NTicks-WindowWidth)*WindowWidth*NBasis)
+                 running time. If you work  in  time-constrained  setting  and
+                 have to analyze just a few last ticks, choosing NTicks  equal
+                 to WindowWidth+SmoothingLen, with SmoothingLen=1...WindowWidth
+                 will result in good compromise between noise cancellation and
+                 analysis speed.
+
+    INPUT PARAMETERS:
+        S               -   SSA model
+        Data            -   array[NTicks], can be larger (only NTicks  leading
+                            elements will be used)
+        NTicks          -   number of ticks to analyze, Nticks>=1.
+                            * special case of NTicks<WindowWidth  is   handled
+                              by returning zeros as trend, and signal as noise
+
+    OUTPUT PARAMETERS:
+        Trend           -   array[NTicks], reconstructed trend line
+        Noise           -   array[NTicks], the rest of the signal;
+                            it holds that ActualData = Trend+Noise.
+
+
+    CACHING/REUSE OF THE BASIS
+
+    Caching/reuse of previous results is performed:
+    * first call performs full run of SSA; basis is stored in the cache
+    * subsequent calls reuse previously cached basis
+    * if you call any function which changes model properties (window  length,
+      algorithm, dataset), internal basis will be invalidated.
+    * the only calls which do NOT invalidate basis are listed below:
+      a) ssasetwindow() with same window length
+      b) ssaappendpointandupdate()
+      c) ssaappendsequenceandupdate()
+      d) ssasetalgotopk...() with exactly same K
+      Calling these functions will result in reuse of previously found basis.
+
+    In  any  case,  only  basis  is  reused. Reconstruction is performed  from
+    scratch every time you call this function.
+
+
+    HANDLING OF DEGENERATE CASES
+
+    Following degenerate cases may happen:
+    * dataset is empty (no analysis can be done)
+    * all sequences are shorter than the window length,no analysis can be done
+    * no algorithm is specified (no analysis can be done)
+    * sequence being passed is shorter than the window length
+
+    Calling this function in degenerate cases returns following result:
+    * in any case, NTicks ticks is returned
+    * trend is assumed to be zero
+    * noise is initialized by the sequence.
+
+    No analysis is performed in degenerate cases (we immediately return  dummy
+    values, no basis is constructed).
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssaanalyzesequence(ssamodel s, double[] data, int nticks, out double[] trend, out double[] noise)
+    {
+        trend = new double[0];
+        noise = new double[0];
+        ssa.ssaanalyzesequence(s.innerobj, data, nticks, ref trend, ref noise, null);
+    }
+    
+    public static void ssaanalyzesequence(ssamodel s, double[] data, int nticks, out double[] trend, out double[] noise, alglib.xparams _params)
+    {
+        trend = new double[0];
+        noise = new double[0];
+        ssa.ssaanalyzesequence(s.innerobj, data, nticks, ref trend, ref noise, _params);
+    }
+            
+    public static void ssaanalyzesequence(ssamodel s, double[] data, out double[] trend, out double[] noise)
+    {
+        int nticks;
+    
+        trend = new double[0];
+        noise = new double[0];
+        nticks = ap.len(data);
+        ssa.ssaanalyzesequence(s.innerobj, data, nticks, ref trend, ref noise, null);
+    
+        return;
+    }
+            
+    public static void ssaanalyzesequence(ssamodel s, double[] data, out double[] trend, out double[] noise, alglib.xparams _params)
+    {
+        int nticks;
+    
+        trend = new double[0];
+        noise = new double[0];
+        nticks = ap.len(data);
+        ssa.ssaanalyzesequence(s.innerobj, data, nticks, ref trend, ref noise, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function builds SSA basis and performs forecasting  for  a  specified
+    number of ticks, returning value of trend.
+
+    Forecast is performed as follows:
+    * SSA  trend  extraction  is  applied  to last WindowWidth elements of the
+      internally stored dataset; this step is basically a noise reduction.
+    * linear recurrence relation is applied to extracted trend
+
+    This function has following running time:
+    * O(NBasis*WindowWidth) for trend extraction phase (always performed)
+    * O(WindowWidth*NTicks) for forecast phase
+
+    NOTE: noise reduction is ALWAYS applied by this algorithm; if you want  to
+          apply recurrence relation  to  raw  unprocessed  data,  use  another
+          function - ssaforecastsequence() which allows to  turn  on  and  off
+          noise reduction phase.
+
+    NOTE: this algorithm performs prediction using only one - last  -  sliding
+          window.  Predictions  produced   by   such   approach   are   smooth
+          continuations of the reconstructed  trend  line,  but  they  can  be
+          easily corrupted by noise. If you need  noise-resistant  prediction,
+          use ssaforecastavglast() function, which averages predictions  built
+          using several sliding windows.
+
+    INPUT PARAMETERS:
+        S               -   SSA model
+        NTicks          -   number of ticks to forecast, NTicks>=1
+
+    OUTPUT PARAMETERS:
+        Trend           -   array[NTicks], predicted trend line
+
+
+    CACHING/REUSE OF THE BASIS
+
+    Caching/reuse of previous results is performed:
+    * first call performs full run of SSA; basis is stored in the cache
+    * subsequent calls reuse previously cached basis
+    * if you call any function which changes model properties (window  length,
+      algorithm, dataset), internal basis will be invalidated.
+    * the only calls which do NOT invalidate basis are listed below:
+      a) ssasetwindow() with same window length
+      b) ssaappendpointandupdate()
+      c) ssaappendsequenceandupdate()
+      d) ssasetalgotopk...() with exactly same K
+      Calling these functions will result in reuse of previously found basis.
+
+
+    HANDLING OF DEGENERATE CASES
+
+    Following degenerate cases may happen:
+    * dataset is empty (no analysis can be done)
+    * all sequences are shorter than the window length,no analysis can be done
+    * no algorithm is specified (no analysis can be done)
+    * last sequence is shorter than the WindowWidth   (analysis  can  be done,
+      but we can not perform forecasting on the last sequence)
+    * window lentgh is 1 (impossible to use for forecasting)
+    * SSA analysis algorithm is  configured  to  extract  basis  whose size is
+      equal to window length (impossible to use for  forecasting;  only  basis
+      whose size is less than window length can be used).
+
+    Calling this function in degenerate cases returns following result:
+    * NTicks  copies  of  the  last  value is returned for non-empty task with
+      large enough dataset, but with overcomplete  basis  (window  width=1  or
+      basis size is equal to window width)
+    * zero trend with length=NTicks is returned for empty task
+
+    No analysis is performed in degenerate cases (we immediately return  dummy
+    values, no basis is ever constructed).
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssaforecastlast(ssamodel s, int nticks, out double[] trend)
+    {
+        trend = new double[0];
+        ssa.ssaforecastlast(s.innerobj, nticks, ref trend, null);
+    }
+    
+    public static void ssaforecastlast(ssamodel s, int nticks, out double[] trend, alglib.xparams _params)
+    {
+        trend = new double[0];
+        ssa.ssaforecastlast(s.innerobj, nticks, ref trend, _params);
+    }
+    
+    /*************************************************************************
+    This function builds SSA  basis  and  performs  forecasting  for  a  user-
+    specified sequence, returning value of trend.
+
+    Forecasting is done in two stages:
+    * first,  we  extract  trend  from the WindowWidth  last  elements of  the
+      sequence. This stage is optional, you  can  turn  it  off  if  you  pass
+      data which are already processed with SSA. Of course, you  can  turn  it
+      off even for raw data, but it is not recommended - noise suppression  is
+      very important for correct prediction.
+    * then, we apply LRR for last  WindowWidth-1  elements  of  the  extracted
+      trend.
+
+    This function has following running time:
+    * O(NBasis*WindowWidth) for trend extraction phase
+    * O(WindowWidth*NTicks) for forecast phase
+
+    NOTE: this algorithm performs prediction using only one - last  -  sliding
+          window.  Predictions  produced   by   such   approach   are   smooth
+          continuations of the reconstructed  trend  line,  but  they  can  be
+          easily corrupted by noise. If you need  noise-resistant  prediction,
+          use ssaforecastavgsequence() function,  which  averages  predictions
+          built using several sliding windows.
+
+    INPUT PARAMETERS:
+        S               -   SSA model
+        Data            -   array[NTicks], data to forecast
+        DataLen         -   number of ticks in the data, DataLen>=1
+        ForecastLen     -   number of ticks to predict, ForecastLen>=1
+        ApplySmoothing  -   whether to apply smoothing trend extraction or not;
+                            if you do not know what to specify, pass True.
+
+    OUTPUT PARAMETERS:
+        Trend           -   array[ForecastLen], forecasted trend
+
+
+    CACHING/REUSE OF THE BASIS
+
+    Caching/reuse of previous results is performed:
+    * first call performs full run of SSA; basis is stored in the cache
+    * subsequent calls reuse previously cached basis
+    * if you call any function which changes model properties (window  length,
+      algorithm, dataset), internal basis will be invalidated.
+    * the only calls which do NOT invalidate basis are listed below:
+      a) ssasetwindow() with same window length
+      b) ssaappendpointandupdate()
+      c) ssaappendsequenceandupdate()
+      d) ssasetalgotopk...() with exactly same K
+      Calling these functions will result in reuse of previously found basis.
+
+
+    HANDLING OF DEGENERATE CASES
+
+    Following degenerate cases may happen:
+    * dataset is empty (no analysis can be done)
+    * all sequences are shorter than the window length,no analysis can be done
+    * no algorithm is specified (no analysis can be done)
+    * data sequence is shorter than the WindowWidth   (analysis  can  be done,
+      but we can not perform forecasting on the last sequence)
+    * window lentgh is 1 (impossible to use for forecasting)
+    * SSA analysis algorithm is  configured  to  extract  basis  whose size is
+      equal to window length (impossible to use for  forecasting;  only  basis
+      whose size is less than window length can be used).
+
+    Calling this function in degenerate cases returns following result:
+    * ForecastLen copies of the last value is returned for non-empty task with
+      large enough dataset, but with overcomplete  basis  (window  width=1  or
+      basis size is equal to window width)
+    * zero trend with length=ForecastLen is returned for empty task
+
+    No analysis is performed in degenerate cases (we immediately return  dummy
+    values, no basis is ever constructed).
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssaforecastsequence(ssamodel s, double[] data, int datalen, int forecastlen, bool applysmoothing, out double[] trend)
+    {
+        trend = new double[0];
+        ssa.ssaforecastsequence(s.innerobj, data, datalen, forecastlen, applysmoothing, ref trend, null);
+    }
+    
+    public static void ssaforecastsequence(ssamodel s, double[] data, int datalen, int forecastlen, bool applysmoothing, out double[] trend, alglib.xparams _params)
+    {
+        trend = new double[0];
+        ssa.ssaforecastsequence(s.innerobj, data, datalen, forecastlen, applysmoothing, ref trend, _params);
+    }
+            
+    public static void ssaforecastsequence(ssamodel s, double[] data, int forecastlen, out double[] trend)
+    {
+        int datalen;
+        bool applysmoothing;
+    
+        trend = new double[0];
+        datalen = ap.len(data);
+        applysmoothing = true;
+        ssa.ssaforecastsequence(s.innerobj, data, datalen, forecastlen, applysmoothing, ref trend, null);
+    
+        return;
+    }
+            
+    public static void ssaforecastsequence(ssamodel s, double[] data, int forecastlen, out double[] trend, alglib.xparams _params)
+    {
+        int datalen;
+        bool applysmoothing;
+    
+        trend = new double[0];
+        datalen = ap.len(data);
+        applysmoothing = true;
+        ssa.ssaforecastsequence(s.innerobj, data, datalen, forecastlen, applysmoothing, ref trend, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function builds SSA basis and performs forecasting  for  a  specified
+    number of ticks, returning value of trend.
+
+    Forecast is performed as follows:
+    * SSA  trend  extraction  is  applied to last  M  sliding windows  of  the
+      internally stored dataset
+    * for each of M sliding windows, M predictions are built
+    * average value of M predictions is returned
+
+    This function has following running time:
+    * O(NBasis*WindowWidth*M) for trend extraction phase (always performed)
+    * O(WindowWidth*NTicks*M) for forecast phase
+
+    NOTE: noise reduction is ALWAYS applied by this algorithm; if you want  to
+          apply recurrence relation  to  raw  unprocessed  data,  use  another
+          function - ssaforecastsequence() which allows to  turn  on  and  off
+          noise reduction phase.
+
+    NOTE: combination of several predictions results in lesser sensitivity  to
+          noise, but it may produce undesirable discontinuities  between  last
+          point of the trend and first point of the prediction. The reason  is
+          that  last  point  of  the  trend is usually corrupted by noise, but
+          average  value of  several  predictions  is less sensitive to noise,
+          thus discontinuity appears. It is not a bug.
+
+    INPUT PARAMETERS:
+        S               -   SSA model
+        M               -   number  of  sliding  windows  to combine, M>=1. If
+                            your dataset has less than M sliding windows, this
+                            parameter will be silently reduced.
+        NTicks          -   number of ticks to forecast, NTicks>=1
+
+    OUTPUT PARAMETERS:
+        Trend           -   array[NTicks], predicted trend line
+
+
+    CACHING/REUSE OF THE BASIS
+
+    Caching/reuse of previous results is performed:
+    * first call performs full run of SSA; basis is stored in the cache
+    * subsequent calls reuse previously cached basis
+    * if you call any function which changes model properties (window  length,
+      algorithm, dataset), internal basis will be invalidated.
+    * the only calls which do NOT invalidate basis are listed below:
+      a) ssasetwindow() with same window length
+      b) ssaappendpointandupdate()
+      c) ssaappendsequenceandupdate()
+      d) ssasetalgotopk...() with exactly same K
+      Calling these functions will result in reuse of previously found basis.
+
+
+    HANDLING OF DEGENERATE CASES
+
+    Following degenerate cases may happen:
+    * dataset is empty (no analysis can be done)
+    * all sequences are shorter than the window length,no analysis can be done
+    * no algorithm is specified (no analysis can be done)
+    * last sequence is shorter than the WindowWidth   (analysis  can  be done,
+      but we can not perform forecasting on the last sequence)
+    * window lentgh is 1 (impossible to use for forecasting)
+    * SSA analysis algorithm is  configured  to  extract  basis  whose size is
+      equal to window length (impossible to use for  forecasting;  only  basis
+      whose size is less than window length can be used).
+
+    Calling this function in degenerate cases returns following result:
+    * NTicks  copies  of  the  last  value is returned for non-empty task with
+      large enough dataset, but with overcomplete  basis  (window  width=1  or
+      basis size is equal to window width)
+    * zero trend with length=NTicks is returned for empty task
+
+    No analysis is performed in degenerate cases (we immediately return  dummy
+    values, no basis is ever constructed).
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssaforecastavglast(ssamodel s, int m, int nticks, out double[] trend)
+    {
+        trend = new double[0];
+        ssa.ssaforecastavglast(s.innerobj, m, nticks, ref trend, null);
+    }
+    
+    public static void ssaforecastavglast(ssamodel s, int m, int nticks, out double[] trend, alglib.xparams _params)
+    {
+        trend = new double[0];
+        ssa.ssaforecastavglast(s.innerobj, m, nticks, ref trend, _params);
+    }
+    
+    /*************************************************************************
+    This function builds SSA  basis  and  performs  forecasting  for  a  user-
+    specified sequence, returning value of trend.
+
+    Forecasting is done in two stages:
+    * first,  we  extract  trend  from M last sliding windows of the sequence.
+      This stage is optional, you can  turn  it  off  if  you  pass data which
+      are already processed with SSA. Of course, you  can  turn  it  off  even
+      for raw data, but it is not recommended  -  noise  suppression  is  very
+      important for correct prediction.
+    * then, we apply LRR independently for M sliding windows
+    * average of M predictions is returned
+
+    This function has following running time:
+    * O(NBasis*WindowWidth*M) for trend extraction phase
+    * O(WindowWidth*NTicks*M) for forecast phase
+
+    NOTE: combination of several predictions results in lesser sensitivity  to
+          noise, but it may produce undesirable discontinuities  between  last
+          point of the trend and first point of the prediction. The reason  is
+          that  last  point  of  the  trend is usually corrupted by noise, but
+          average  value of  several  predictions  is less sensitive to noise,
+          thus discontinuity appears. It is not a bug.
+
+    INPUT PARAMETERS:
+        S               -   SSA model
+        Data            -   array[NTicks], data to forecast
+        DataLen         -   number of ticks in the data, DataLen>=1
+        M               -   number  of  sliding  windows  to combine, M>=1. If
+                            your dataset has less than M sliding windows, this
+                            parameter will be silently reduced.
+        ForecastLen     -   number of ticks to predict, ForecastLen>=1
+        ApplySmoothing  -   whether to apply smoothing trend extraction or not.
+                            if you do not know what to specify, pass true.
+
+    OUTPUT PARAMETERS:
+        Trend           -   array[ForecastLen], forecasted trend
+
+
+    CACHING/REUSE OF THE BASIS
+
+    Caching/reuse of previous results is performed:
+    * first call performs full run of SSA; basis is stored in the cache
+    * subsequent calls reuse previously cached basis
+    * if you call any function which changes model properties (window  length,
+      algorithm, dataset), internal basis will be invalidated.
+    * the only calls which do NOT invalidate basis are listed below:
+      a) ssasetwindow() with same window length
+      b) ssaappendpointandupdate()
+      c) ssaappendsequenceandupdate()
+      d) ssasetalgotopk...() with exactly same K
+      Calling these functions will result in reuse of previously found basis.
+
+
+    HANDLING OF DEGENERATE CASES
+
+    Following degenerate cases may happen:
+    * dataset is empty (no analysis can be done)
+    * all sequences are shorter than the window length,no analysis can be done
+    * no algorithm is specified (no analysis can be done)
+    * data sequence is shorter than the WindowWidth   (analysis  can  be done,
+      but we can not perform forecasting on the last sequence)
+    * window lentgh is 1 (impossible to use for forecasting)
+    * SSA analysis algorithm is  configured  to  extract  basis  whose size is
+      equal to window length (impossible to use for  forecasting;  only  basis
+      whose size is less than window length can be used).
+
+    Calling this function in degenerate cases returns following result:
+    * ForecastLen copies of the last value is returned for non-empty task with
+      large enough dataset, but with overcomplete  basis  (window  width=1  or
+      basis size is equal to window width)
+    * zero trend with length=ForecastLen is returned for empty task
+
+    No analysis is performed in degenerate cases (we immediately return  dummy
+    values, no basis is ever constructed).
+
+      -- ALGLIB --
+         Copyright 30.10.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void ssaforecastavgsequence(ssamodel s, double[] data, int datalen, int m, int forecastlen, bool applysmoothing, out double[] trend)
+    {
+        trend = new double[0];
+        ssa.ssaforecastavgsequence(s.innerobj, data, datalen, m, forecastlen, applysmoothing, ref trend, null);
+    }
+    
+    public static void ssaforecastavgsequence(ssamodel s, double[] data, int datalen, int m, int forecastlen, bool applysmoothing, out double[] trend, alglib.xparams _params)
+    {
+        trend = new double[0];
+        ssa.ssaforecastavgsequence(s.innerobj, data, datalen, m, forecastlen, applysmoothing, ref trend, _params);
+    }
+            
+    public static void ssaforecastavgsequence(ssamodel s, double[] data, int m, int forecastlen, out double[] trend)
+    {
+        int datalen;
+        bool applysmoothing;
+    
+        trend = new double[0];
+        datalen = ap.len(data);
+        applysmoothing = true;
+        ssa.ssaforecastavgsequence(s.innerobj, data, datalen, m, forecastlen, applysmoothing, ref trend, null);
+    
+        return;
+    }
+            
+    public static void ssaforecastavgsequence(ssamodel s, double[] data, int m, int forecastlen, out double[] trend, alglib.xparams _params)
+    {
+        int datalen;
+        bool applysmoothing;
+    
+        trend = new double[0];
+        datalen = ap.len(data);
+        applysmoothing = true;
+        ssa.ssaforecastavgsequence(s.innerobj, data, datalen, m, forecastlen, applysmoothing, ref trend, _params);
+    
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class linearmodel : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public linearmodel()
+        {
+            _innerobj = new linreg.linearmodel();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new linearmodel((linreg.linearmodel)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new lrreport((linreg.lrreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void lrbuild(double[,] xy, int npoints, int nvars, out int info, out linearmodel lm, out lrreport ar, alglib.xparams _params)
+    {
+        info = 0;
+        lm = new linearmodel();
+        ar = new lrreport();
+        linreg.lrbuild(xy, npoints, nvars, ref info, lm.innerobj, ar.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lrbuilds(double[,] xy, double[] s, int npoints, int nvars, out int info, out linearmodel lm, out lrreport ar, alglib.xparams _params)
+    {
+        info = 0;
+        lm = new linearmodel();
+        ar = new lrreport();
+        linreg.lrbuilds(xy, s, npoints, nvars, ref info, lm.innerobj, ar.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lrbuildzs(double[,] xy, double[] s, int npoints, int nvars, out int info, out linearmodel lm, out lrreport ar, alglib.xparams _params)
+    {
+        info = 0;
+        lm = new linearmodel();
+        ar = new lrreport();
+        linreg.lrbuildzs(xy, s, npoints, nvars, ref info, lm.innerobj, ar.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lrbuildz(double[,] xy, int npoints, int nvars, out int info, out linearmodel lm, out lrreport ar, alglib.xparams _params)
+    {
+        info = 0;
+        lm = new linearmodel();
+        ar = new lrreport();
+        linreg.lrbuildz(xy, npoints, nvars, ref info, lm.innerobj, ar.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lrunpack(linearmodel lm, out double[] v, out int nvars, alglib.xparams _params)
+    {
+        v = new double[0];
+        nvars = 0;
+        linreg.lrunpack(lm.innerobj, ref v, ref nvars, _params);
+    }
+    
+    /*************************************************************************
+    "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, null);
+    }
+    
+    public static void lrpack(double[] v, int nvars, out linearmodel lm, alglib.xparams _params)
+    {
+        lm = new linearmodel();
+        linreg.lrpack(v, nvars, lm.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return linreg.lrprocess(lm.innerobj, x, null);
+    }
+    
+    public static double lrprocess(linearmodel lm, double[] x, alglib.xparams _params)
+    {
+    
+        return linreg.lrprocess(lm.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return linreg.lrrmserror(lm.innerobj, xy, npoints, null);
+    }
+    
+    public static double lrrmserror(linearmodel lm, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return linreg.lrrmserror(lm.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return linreg.lravgerror(lm.innerobj, xy, npoints, null);
+    }
+    
+    public static double lravgerror(linearmodel lm, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return linreg.lravgerror(lm.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return linreg.lravgrelerror(lm.innerobj, xy, npoints, null);
+    }
+    
+    public static double lravgrelerror(linearmodel lm, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return linreg.lravgrelerror(lm.innerobj, xy, npoints, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void filtersma(ref double[] x, int n, int k, alglib.xparams _params)
+    {
+    
+        filters.filtersma(ref x, n, k, _params);
+    }
+            
+    public static void filtersma(ref double[] x, int k)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        filters.filtersma(ref x, n, k, null);
+    
+        return;
+    }
+            
+    public static void filtersma(ref double[] x, int k, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        filters.filtersma(ref x, n, k, _params);
+    
+        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, null);
+    }
+    
+    public static void filterema(ref double[] x, int n, double alpha, alglib.xparams _params)
+    {
+    
+        filters.filterema(ref x, n, alpha, _params);
+    }
+            
+    public static void filterema(ref double[] x, double alpha)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        filters.filterema(ref x, n, alpha, null);
+    
+        return;
+    }
+            
+    public static void filterema(ref double[] x, double alpha, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        filters.filterema(ref x, n, alpha, _params);
+    
+        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, null);
+    }
+    
+    public static void filterlrma(ref double[] x, int n, int k, alglib.xparams _params)
+    {
+    
+        filters.filterlrma(ref x, n, k, _params);
+    }
+            
+    public static void filterlrma(ref double[] x, int k)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        filters.filterlrma(ref x, n, k, null);
+    
+        return;
+    }
+            
+    public static void filterlrma(ref double[] x, int k, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        filters.filterlrma(ref x, n, k, _params);
+    
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class logitmodel : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public logitmodel()
+        {
+            _innerobj = new logit.logitmodel();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new logitmodel((logit.logitmodel)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new mnlreport((logit.mnlreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void mnltrainh(double[,] xy, int npoints, int nvars, int nclasses, out int info, out logitmodel lm, out mnlreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        lm = new logitmodel();
+        rep = new mnlreport();
+        logit.mnltrainh(xy, npoints, nvars, nclasses, ref info, lm.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mnlprocess(logitmodel lm, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        logit.mnlprocess(lm.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    '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, null);
+    }
+    
+    public static void mnlprocessi(logitmodel lm, double[] x, out double[] y, alglib.xparams _params)
+    {
+        y = new double[0];
+        logit.mnlprocessi(lm.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mnlunpack(logitmodel lm, out double[,] a, out int nvars, out int nclasses, alglib.xparams _params)
+    {
+        a = new double[0,0];
+        nvars = 0;
+        nclasses = 0;
+        logit.mnlunpack(lm.innerobj, ref a, ref nvars, ref nclasses, _params);
+    }
+    
+    /*************************************************************************
+    "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, null);
+    }
+    
+    public static void mnlpack(double[,] a, int nvars, int nclasses, out logitmodel lm, alglib.xparams _params)
+    {
+        lm = new logitmodel();
+        logit.mnlpack(a, nvars, nclasses, lm.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return logit.mnlavgce(lm.innerobj, xy, npoints, null);
+    }
+    
+    public static double mnlavgce(logitmodel lm, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return logit.mnlavgce(lm.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return logit.mnlrelclserror(lm.innerobj, xy, npoints, null);
+    }
+    
+    public static double mnlrelclserror(logitmodel lm, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return logit.mnlrelclserror(lm.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return logit.mnlrmserror(lm.innerobj, xy, npoints, null);
+    }
+    
+    public static double mnlrmserror(logitmodel lm, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return logit.mnlrmserror(lm.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return logit.mnlavgerror(lm.innerobj, xy, npoints, null);
+    }
+    
+    public static double mnlavgerror(logitmodel lm, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return logit.mnlavgerror(lm.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return logit.mnlavgrelerror(lm.innerobj, xy, ssize, null);
+    }
+    
+    public static double mnlavgrelerror(logitmodel lm, double[,] xy, int ssize, alglib.xparams _params)
+    {
+    
+        return logit.mnlavgrelerror(lm.innerobj, xy, ssize, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return logit.mnlclserror(lm.innerobj, xy, npoints, null);
+    }
+    
+    public static int mnlclserror(logitmodel lm, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return logit.mnlclserror(lm.innerobj, xy, npoints, _params);
+    }
+
+}
+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 : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public mcpdstate()
+        {
+            _innerobj = new mcpd.mcpdstate();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new mcpdstate((mcpd.mcpdstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new mcpdreport((mcpd.mcpdreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void mcpdcreate(int n, out mcpdstate s, alglib.xparams _params)
+    {
+        s = new mcpdstate();
+        mcpd.mcpdcreate(n, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mcpdcreateentry(int n, int entrystate, out mcpdstate s, alglib.xparams _params)
+    {
+        s = new mcpdstate();
+        mcpd.mcpdcreateentry(n, entrystate, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mcpdcreateexit(int n, int exitstate, out mcpdstate s, alglib.xparams _params)
+    {
+        s = new mcpdstate();
+        mcpd.mcpdcreateexit(n, exitstate, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mcpdcreateentryexit(int n, int entrystate, int exitstate, out mcpdstate s, alglib.xparams _params)
+    {
+        s = new mcpdstate();
+        mcpd.mcpdcreateentryexit(n, entrystate, exitstate, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mcpdaddtrack(mcpdstate s, double[,] xy, int k, alglib.xparams _params)
+    {
+    
+        mcpd.mcpdaddtrack(s.innerobj, xy, k, _params);
+    }
+            
+    public static void mcpdaddtrack(mcpdstate s, double[,] xy)
+    {
+        int k;
+    
+    
+        k = ap.rows(xy);
+        mcpd.mcpdaddtrack(s.innerobj, xy, k, null);
+    
+        return;
+    }
+            
+    public static void mcpdaddtrack(mcpdstate s, double[,] xy, alglib.xparams _params)
+    {
+        int k;
+    
+    
+        k = ap.rows(xy);
+        mcpd.mcpdaddtrack(s.innerobj, xy, k, _params);
+    
+        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, null);
+    }
+    
+    public static void mcpdsetec(mcpdstate s, double[,] ec, alglib.xparams _params)
+    {
+    
+        mcpd.mcpdsetec(s.innerobj, ec, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mcpdaddec(mcpdstate s, int i, int j, double c, alglib.xparams _params)
+    {
+    
+        mcpd.mcpdaddec(s.innerobj, i, j, c, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mcpdsetbc(mcpdstate s, double[,] bndl, double[,] bndu, alglib.xparams _params)
+    {
+    
+        mcpd.mcpdsetbc(s.innerobj, bndl, bndu, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mcpdaddbc(mcpdstate s, int i, int j, double bndl, double bndu, alglib.xparams _params)
+    {
+    
+        mcpd.mcpdaddbc(s.innerobj, i, j, bndl, bndu, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mcpdsetlc(mcpdstate s, double[,] c, int[] ct, int k, alglib.xparams _params)
+    {
+    
+        mcpd.mcpdsetlc(s.innerobj, c, ct, k, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void mcpdsetlc(mcpdstate s, double[,] c, int[] ct, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void mcpdsettikhonovregularizer(mcpdstate s, double v, alglib.xparams _params)
+    {
+    
+        mcpd.mcpdsettikhonovregularizer(s.innerobj, v, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mcpdsetprior(mcpdstate s, double[,] pp, alglib.xparams _params)
+    {
+    
+        mcpd.mcpdsetprior(s.innerobj, pp, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mcpdsetpredictionweights(mcpdstate s, double[] pw, alglib.xparams _params)
+    {
+    
+        mcpd.mcpdsetpredictionweights(s.innerobj, pw, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mcpdsolve(mcpdstate s, alglib.xparams _params)
+    {
+    
+        mcpd.mcpdsolve(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mcpdresults(mcpdstate s, out double[,] p, out mcpdreport rep, alglib.xparams _params)
+    {
+        p = new double[0,0];
+        rep = new mcpdreport();
+        mcpd.mcpdresults(s.innerobj, ref p, rep.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Neural networks ensemble
+    *************************************************************************/
+    public class mlpensemble : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public mlpensemble()
+        {
+            _innerobj = new mlpe.mlpensemble();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new mlpensemble((mlpe.mlpensemble)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+        s.sstart_str();
+        mlpe.mlpeserialize(s, obj.innerobj, null);
+        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, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to stream.
+    
+    Data stream generated by this function is same as  string  representation
+    generated  by  string  version  of  serializer - alphanumeric characters,
+    dots, underscores, minus signs, which are grouped into words separated by
+    spaces and CR+LF.
+    
+    We recommend you to read comments on string version of serializer to find
+    out more about serialization of AlGLIB objects.
+    *************************************************************************/
+    public static void mlpeserialize(mlpensemble obj, System.IO.Stream stream_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        mlpe.mlpealloc(s, obj.innerobj, null);
+        s.sstart_stream(stream_out);
+        mlpe.mlpeserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from stream.
+    *************************************************************************/
+    public static void mlpeunserialize(System.IO.Stream stream_in, out mlpensemble obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new mlpensemble();
+        s.ustart_stream(stream_in);
+        mlpe.mlpeunserialize(s, obj.innerobj, null);
+        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, null);
+    }
+    
+    public static void mlpecreate0(int nin, int nout, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreate0(nin, nout, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpecreate1(int nin, int nhid, int nout, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreate1(nin, nhid, nout, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpecreate2(int nin, int nhid1, int nhid2, int nout, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreate2(nin, nhid1, nhid2, nout, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpecreateb0(int nin, int nout, double b, double d, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreateb0(nin, nout, b, d, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpecreateb1(int nin, int nhid, int nout, double b, double d, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreateb1(nin, nhid, nout, b, d, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpecreateb2(int nin, int nhid1, int nhid2, int nout, double b, double d, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreateb2(nin, nhid1, nhid2, nout, b, d, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpecreater0(int nin, int nout, double a, double b, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreater0(nin, nout, a, b, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpecreater1(int nin, int nhid, int nout, double a, double b, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreater1(nin, nhid, nout, a, b, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpecreater2(int nin, int nhid1, int nhid2, int nout, double a, double b, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreater2(nin, nhid1, nhid2, nout, a, b, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpecreatec0(int nin, int nout, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreatec0(nin, nout, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpecreatec1(int nin, int nhid, int nout, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreatec1(nin, nhid, nout, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpecreatec2(int nin, int nhid1, int nhid2, int nout, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreatec2(nin, nhid1, nhid2, nout, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpecreatefromnetwork(multilayerperceptron network, int ensemblesize, out mlpensemble ensemble, alglib.xparams _params)
+    {
+        ensemble = new mlpensemble();
+        mlpe.mlpecreatefromnetwork(network.innerobj, ensemblesize, ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Randomization of MLP ensemble
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlperandomize(mlpensemble ensemble)
+    {
+    
+        mlpe.mlperandomize(ensemble.innerobj, null);
+    }
+    
+    public static void mlperandomize(mlpensemble ensemble, alglib.xparams _params)
+    {
+    
+        mlpe.mlperandomize(ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpeproperties(mlpensemble ensemble, out int nin, out int nout, alglib.xparams _params)
+    {
+        nin = 0;
+        nout = 0;
+        mlpe.mlpeproperties(ensemble.innerobj, ref nin, ref nout, _params);
+    }
+    
+    /*************************************************************************
+    Return normalization type (whether ensemble is SOFTMAX-normalized or not).
+
+      -- ALGLIB --
+         Copyright 17.02.2009 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool mlpeissoftmax(mlpensemble ensemble)
+    {
+    
+        return mlpe.mlpeissoftmax(ensemble.innerobj, null);
+    }
+    
+    public static bool mlpeissoftmax(mlpensemble ensemble, alglib.xparams _params)
+    {
+    
+        return mlpe.mlpeissoftmax(ensemble.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlpeprocess(mlpensemble ensemble, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        mlpe.mlpeprocess(ensemble.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    '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, null);
+    }
+    
+    public static void mlpeprocessi(mlpensemble ensemble, double[] x, out double[] y, alglib.xparams _params)
+    {
+        y = new double[0];
+        mlpe.mlpeprocessi(ensemble.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return mlpe.mlperelclserror(ensemble.innerobj, xy, npoints, null);
+    }
+    
+    public static double mlperelclserror(mlpensemble ensemble, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpe.mlperelclserror(ensemble.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return mlpe.mlpeavgce(ensemble.innerobj, xy, npoints, null);
+    }
+    
+    public static double mlpeavgce(mlpensemble ensemble, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpe.mlpeavgce(ensemble.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return mlpe.mlpermserror(ensemble.innerobj, xy, npoints, null);
+    }
+    
+    public static double mlpermserror(mlpensemble ensemble, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpe.mlpermserror(ensemble.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return mlpe.mlpeavgerror(ensemble.innerobj, xy, npoints, null);
+    }
+    
+    public static double mlpeavgerror(mlpensemble ensemble, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpe.mlpeavgerror(ensemble.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return mlpe.mlpeavgrelerror(ensemble.innerobj, xy, npoints, null);
+    }
+    
+    public static double mlpeavgrelerror(mlpensemble ensemble, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return mlpe.mlpeavgrelerror(ensemble.innerobj, xy, npoints, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Training report:
+        * RelCLSError   -   fraction of misclassified cases.
+        * AvgCE         -   acerage cross-entropy
+        * RMSError      -   root-mean-square error
+        * AvgError      -   average error
+        * AvgRelError   -   average relative error
+        * NGrad         -   number of gradient calculations
+        * NHess         -   number of Hessian calculations
+        * NCholesky     -   number of Cholesky decompositions
+
+    NOTE 1: RelCLSError/AvgCE are zero on regression problems.
+
+    NOTE 2: on classification problems  RMSError/AvgError/AvgRelError  contain
+            errors in prediction of posterior probabilities
+    *************************************************************************/
+    public class mlpreport : alglibobject
+    {
+        //
+        // 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 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new mlpreport((mlptrain.mlpreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new mlpcvreport((mlptrain.mlpcvreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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;
+        }
+    }
+
+
+    /*************************************************************************
+    Trainer object for neural network.
+
+    You should not try to access fields of this object directly -  use  ALGLIB
+    functions to work with this object.
+    *************************************************************************/
+    public class mlptrainer : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public mlptrainer()
+        {
+            _innerobj = new mlptrain.mlptrainer();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new mlptrainer((mlptrain.mlptrainer)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private mlptrain.mlptrainer _innerobj;
+        public mlptrain.mlptrainer innerobj { get { return _innerobj; } }
+        public mlptrainer(mlptrain.mlptrainer 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, null);
+    }
+    
+    public static void mlptrainlm(multilayerperceptron network, double[,] xy, int npoints, double decay, int restarts, out int info, out mlpreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new mlpreport();
+        mlptrain.mlptrainlm(network.innerobj, xy, npoints, decay, restarts, ref info, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlptrainlbfgs(multilayerperceptron network, double[,] xy, int npoints, double decay, int restarts, double wstep, int maxits, out int info, out mlpreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new mlpreport();
+        mlptrain.mlptrainlbfgs(network.innerobj, xy, npoints, decay, restarts, wstep, maxits, ref info, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mlptraines(multilayerperceptron network, double[,] trnxy, int trnsize, double[,] valxy, int valsize, double decay, int restarts, out int info, out mlpreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new mlpreport();
+        mlptrain.mlptraines(network.innerobj, trnxy, trnsize, valxy, valsize, decay, restarts, ref info, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        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, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new mlpreport();
+        cvrep = new mlpcvreport();
+        mlptrain.mlpkfoldcvlm(network.innerobj, xy, npoints, decay, restarts, foldscount, ref info, rep.innerobj, cvrep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function estimates generalization error using cross-validation on the
+    current dataset with current training settings.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        S           -   trainer object
+        Network     -   neural network. It must have same number of inputs and
+                        output/classes as was specified during creation of the
+                        trainer object. Network is not changed  during  cross-
+                        validation and is not trained - it  is  used  only  as
+                        representative of its architecture. I.e., we  estimate
+                        generalization properties of  ARCHITECTURE,  not  some
+                        specific network.
+        NRestarts   -   number of restarts, >=0:
+                        * NRestarts>0  means  that  for  each cross-validation
+                          round   specified  number   of  random  restarts  is
+                          performed,  with  best  network  being  chosen after
+                          training.
+                        * NRestarts=0 is same as NRestarts=1
+        FoldsCount  -   number of folds in k-fold cross-validation:
+                        * 2<=FoldsCount<=size of dataset
+                        * recommended value: 10.
+                        * values larger than dataset size will be silently
+                          truncated down to dataset size
+
+    OUTPUT PARAMETERS:
+        Rep         -   structure which contains cross-validation estimates:
+                        * Rep.RelCLSError - fraction of misclassified cases.
+                        * Rep.AvgCE - acerage cross-entropy
+                        * Rep.RMSError - root-mean-square error
+                        * Rep.AvgError - average error
+                        * Rep.AvgRelError - average relative error
+
+    NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(),
+          or subset with only one point  was  given,  zeros  are  returned  as
+          estimates.
+
+    NOTE: this method performs FoldsCount cross-validation  rounds,  each  one
+          with NRestarts random starts.  Thus,  FoldsCount*NRestarts  networks
+          are trained in total.
+
+    NOTE: Rep.RelCLSError/Rep.AvgCE are zero on regression problems.
+
+    NOTE: on classification problems Rep.RMSError/Rep.AvgError/Rep.AvgRelError
+          contain errors in prediction of posterior probabilities.
+
+      -- ALGLIB --
+         Copyright 23.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpkfoldcv(mlptrainer s, multilayerperceptron network, int nrestarts, int foldscount, out mlpreport rep)
+    {
+        rep = new mlpreport();
+        mlptrain.mlpkfoldcv(s.innerobj, network.innerobj, nrestarts, foldscount, rep.innerobj, null);
+    }
+    
+    public static void mlpkfoldcv(mlptrainer s, multilayerperceptron network, int nrestarts, int foldscount, out mlpreport rep, alglib.xparams _params)
+    {
+        rep = new mlpreport();
+        mlptrain.mlpkfoldcv(s.innerobj, network.innerobj, nrestarts, foldscount, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Creation of the network trainer object for regression networks
+
+    INPUT PARAMETERS:
+        NIn         -   number of inputs, NIn>=1
+        NOut        -   number of outputs, NOut>=1
+
+    OUTPUT PARAMETERS:
+        S           -   neural network trainer object.
+                        This structure can be used to train any regression
+                        network with NIn inputs and NOut outputs.
+
+      -- ALGLIB --
+         Copyright 23.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreatetrainer(int nin, int nout, out mlptrainer s)
+    {
+        s = new mlptrainer();
+        mlptrain.mlpcreatetrainer(nin, nout, s.innerobj, null);
+    }
+    
+    public static void mlpcreatetrainer(int nin, int nout, out mlptrainer s, alglib.xparams _params)
+    {
+        s = new mlptrainer();
+        mlptrain.mlpcreatetrainer(nin, nout, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Creation of the network trainer object for classification networks
+
+    INPUT PARAMETERS:
+        NIn         -   number of inputs, NIn>=1
+        NClasses    -   number of classes, NClasses>=2
+
+    OUTPUT PARAMETERS:
+        S           -   neural network trainer object.
+                        This structure can be used to train any classification
+                        network with NIn inputs and NOut outputs.
+
+      -- ALGLIB --
+         Copyright 23.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpcreatetrainercls(int nin, int nclasses, out mlptrainer s)
+    {
+        s = new mlptrainer();
+        mlptrain.mlpcreatetrainercls(nin, nclasses, s.innerobj, null);
+    }
+    
+    public static void mlpcreatetrainercls(int nin, int nclasses, out mlptrainer s, alglib.xparams _params)
+    {
+        s = new mlptrainer();
+        mlptrain.mlpcreatetrainercls(nin, nclasses, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function sets "current dataset" of the trainer object to  one  passed
+    by user.
+
+    INPUT PARAMETERS:
+        S           -   trainer object
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format. This function checks  correctness
+                        of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                        correct) and throws exception when  incorrect  dataset
+                        is passed.
+        NPoints     -   points count, >=0.
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    datasetformat is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 23.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpsetdataset(mlptrainer s, double[,] xy, int npoints)
+    {
+    
+        mlptrain.mlpsetdataset(s.innerobj, xy, npoints, null);
+    }
+    
+    public static void mlpsetdataset(mlptrainer s, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        mlptrain.mlpsetdataset(s.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    This function sets "current dataset" of the trainer object to  one  passed
+    by user (sparse matrix is used to store dataset).
+
+    INPUT PARAMETERS:
+        S           -   trainer object
+        XY          -   training  set,  see  below  for  information  on   the
+                        training set format. This function checks  correctness
+                        of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                        correct) and throws exception when  incorrect  dataset
+                        is passed. Any  sparse  storage  format  can be  used:
+                        Hash-table, CRS...
+        NPoints     -   points count, >=0
+
+    DATASET FORMAT:
+
+    This  function  uses  two  different  dataset formats - one for regression
+    networks, another one for classification networks.
+
+    For regression networks with NIn inputs and NOut outputs following dataset
+    format is used:
+    * dataset is given by NPoints*(NIn+NOut) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, next NOut columns are outputs
+
+    For classification networks with NIn inputs and NClasses clases  following
+    datasetformat is used:
+    * dataset is given by NPoints*(NIn+1) matrix
+    * each row corresponds to one example
+    * first NIn columns are inputs, last column stores class number (from 0 to
+      NClasses-1).
+
+      -- ALGLIB --
+         Copyright 23.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpsetsparsedataset(mlptrainer s, sparsematrix xy, int npoints)
+    {
+    
+        mlptrain.mlpsetsparsedataset(s.innerobj, xy.innerobj, npoints, null);
+    }
+    
+    public static void mlpsetsparsedataset(mlptrainer s, sparsematrix xy, int npoints, alglib.xparams _params)
+    {
+    
+        mlptrain.mlpsetsparsedataset(s.innerobj, xy.innerobj, npoints, _params);
+    }
+    
+    /*************************************************************************
+    This function sets weight decay coefficient which is used for training.
+
+    INPUT PARAMETERS:
+        S           -   trainer object
+        Decay       -   weight  decay  coefficient,  >=0.  Weight  decay  term
+                        'Decay*||Weights||^2' is added to error  function.  If
+                        you don't know what Decay to choose, use 1.0E-3.
+                        Weight decay can be set to zero,  in this case network
+                        is trained without weight decay.
+
+    NOTE: by default network uses some small nonzero value for weight decay.
+
+      -- ALGLIB --
+         Copyright 23.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpsetdecay(mlptrainer s, double decay)
+    {
+    
+        mlptrain.mlpsetdecay(s.innerobj, decay, null);
+    }
+    
+    public static void mlpsetdecay(mlptrainer s, double decay, alglib.xparams _params)
+    {
+    
+        mlptrain.mlpsetdecay(s.innerobj, decay, _params);
+    }
+    
+    /*************************************************************************
+    This function sets stopping criteria for the optimizer.
+
+    INPUT PARAMETERS:
+        S           -   trainer object
+        WStep       -   stopping criterion. Algorithm stops if  step  size  is
+                        less than WStep. Recommended value - 0.01.  Zero  step
+                        size means stopping after MaxIts iterations.
+                        WStep>=0.
+        MaxIts      -   stopping   criterion.  Algorithm  stops  after  MaxIts
+                        epochs (full passes over entire dataset).  Zero MaxIts
+                        means stopping when step is sufficiently small.
+                        MaxIts>=0.
+
+    NOTE: by default, WStep=0.005 and MaxIts=0 are used. These values are also
+          used when MLPSetCond() is called with WStep=0 and MaxIts=0.
+
+    NOTE: these stopping criteria are used for all kinds of neural training  -
+          from "conventional" networks to early stopping ensembles. When  used
+          for "conventional" networks, they are  used  as  the  only  stopping
+          criteria. When combined with early stopping, they used as ADDITIONAL
+          stopping criteria which can terminate early stopping algorithm.
+
+      -- ALGLIB --
+         Copyright 23.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpsetcond(mlptrainer s, double wstep, int maxits)
+    {
+    
+        mlptrain.mlpsetcond(s.innerobj, wstep, maxits, null);
+    }
+    
+    public static void mlpsetcond(mlptrainer s, double wstep, int maxits, alglib.xparams _params)
+    {
+    
+        mlptrain.mlpsetcond(s.innerobj, wstep, maxits, _params);
+    }
+    
+    /*************************************************************************
+    This function sets training algorithm: batch training using L-BFGS will be
+    used.
+
+    This algorithm:
+    * the most robust for small-scale problems, but may be too slow for  large
+      scale ones.
+    * perfoms full pass through the dataset before performing step
+    * uses conditions specified by MLPSetCond() for stopping
+    * is default one used by trainer object
+
+    INPUT PARAMETERS:
+        S           -   trainer object
+
+      -- ALGLIB --
+         Copyright 23.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpsetalgobatch(mlptrainer s)
+    {
+    
+        mlptrain.mlpsetalgobatch(s.innerobj, null);
+    }
+    
+    public static void mlpsetalgobatch(mlptrainer s, alglib.xparams _params)
+    {
+    
+        mlptrain.mlpsetalgobatch(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function trains neural network passed to this function, using current
+    dataset (one which was passed to MLPSetDataset() or MLPSetSparseDataset())
+    and current training settings. Training  from  NRestarts  random  starting
+    positions is performed, best network is chosen.
+
+    Training is performed using current training algorithm.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        S           -   trainer object
+        Network     -   neural network. It must have same number of inputs and
+                        output/classes as was specified during creation of the
+                        trainer object.
+        NRestarts   -   number of restarts, >=0:
+                        * NRestarts>0 means that specified  number  of  random
+                          restarts are performed, best network is chosen after
+                          training
+                        * NRestarts=0 means that current state of the  network
+                          is used for training.
+
+    OUTPUT PARAMETERS:
+        Network     -   trained network
+
+    NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(),
+          network  is  filled  by zero  values.  Same  behavior  for functions
+          MLPStartTraining and MLPContinueTraining.
+
+    NOTE: this method uses sum-of-squares error function for training.
+
+      -- ALGLIB --
+         Copyright 23.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlptrainnetwork(mlptrainer s, multilayerperceptron network, int nrestarts, out mlpreport rep)
+    {
+        rep = new mlpreport();
+        mlptrain.mlptrainnetwork(s.innerobj, network.innerobj, nrestarts, rep.innerobj, null);
+    }
+    
+    public static void mlptrainnetwork(mlptrainer s, multilayerperceptron network, int nrestarts, out mlpreport rep, alglib.xparams _params)
+    {
+        rep = new mlpreport();
+        mlptrain.mlptrainnetwork(s.innerobj, network.innerobj, nrestarts, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    IMPORTANT: this is an "expert" version of the MLPTrain() function.  We  do
+               not recommend you to use it unless you are pretty sure that you
+               need ability to monitor training progress.
+
+    This function performs step-by-step training of the neural  network.  Here
+    "step-by-step" means that training  starts  with  MLPStartTraining() call,
+    and then user subsequently calls MLPContinueTraining() to perform one more
+    iteration of the training.
+
+    After call to this function trainer object remembers network and  is ready
+    to  train  it.  However,  no  training  is  performed  until first call to
+    MLPContinueTraining() function. Subsequent calls  to MLPContinueTraining()
+    will advance training progress one iteration further.
+
+    EXAMPLE:
+        >
+        > ...initialize network and trainer object....
+        >
+        > MLPStartTraining(Trainer, Network, True)
+        > while MLPContinueTraining(Trainer, Network) do
+        >     ...visualize training progress...
+        >
+
+    INPUT PARAMETERS:
+        S           -   trainer object
+        Network     -   neural network. It must have same number of inputs and
+                        output/classes as was specified during creation of the
+                        trainer object.
+        RandomStart -   randomize network before training or not:
+                        * True  means  that  network  is  randomized  and  its
+                          initial state (one which was passed to  the  trainer
+                          object) is lost.
+                        * False  means  that  training  is  started  from  the
+                          current state of the network
+
+    OUTPUT PARAMETERS:
+        Network     -   neural network which is ready to training (weights are
+                        initialized, preprocessor is initialized using current
+                        training set)
+
+    NOTE: this method uses sum-of-squares error function for training.
+
+    NOTE: it is expected that trainer object settings are NOT  changed  during
+          step-by-step training, i.e. no  one  changes  stopping  criteria  or
+          training set during training. It is possible and there is no defense
+          against  such  actions,  but  algorithm  behavior  in  such cases is
+          undefined and can be unpredictable.
+
+      -- ALGLIB --
+         Copyright 23.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlpstarttraining(mlptrainer s, multilayerperceptron network, bool randomstart)
+    {
+    
+        mlptrain.mlpstarttraining(s.innerobj, network.innerobj, randomstart, null);
+    }
+    
+    public static void mlpstarttraining(mlptrainer s, multilayerperceptron network, bool randomstart, alglib.xparams _params)
+    {
+    
+        mlptrain.mlpstarttraining(s.innerobj, network.innerobj, randomstart, _params);
+    }
+    
+    /*************************************************************************
+    IMPORTANT: this is an "expert" version of the MLPTrain() function.  We  do
+               not recommend you to use it unless you are pretty sure that you
+               need ability to monitor training progress.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    This function performs step-by-step training of the neural  network.  Here
+    "step-by-step" means that training starts  with  MLPStartTraining()  call,
+    and then user subsequently calls MLPContinueTraining() to perform one more
+    iteration of the training.
+
+    This  function  performs  one  more  iteration of the training and returns
+    either True (training continues) or False (training stopped). In case True
+    was returned, Network weights are updated according to the  current  state
+    of the optimization progress. In case False was  returned,  no  additional
+    updates is performed (previous update of  the  network weights moved us to
+    the final point, and no additional updates is needed).
+
+    EXAMPLE:
+        >
+        > [initialize network and trainer object]
+        >
+        > MLPStartTraining(Trainer, Network, True)
+        > while MLPContinueTraining(Trainer, Network) do
+        >     [visualize training progress]
+        >
+
+    INPUT PARAMETERS:
+        S           -   trainer object
+        Network     -   neural  network  structure,  which  is  used to  store
+                        current state of the training process.
+
+    OUTPUT PARAMETERS:
+        Network     -   weights of the neural network  are  rewritten  by  the
+                        current approximation.
+
+    NOTE: this method uses sum-of-squares error function for training.
+
+    NOTE: it is expected that trainer object settings are NOT  changed  during
+          step-by-step training, i.e. no  one  changes  stopping  criteria  or
+          training set during training. It is possible and there is no defense
+          against  such  actions,  but  algorithm  behavior  in  such cases is
+          undefined and can be unpredictable.
+
+    NOTE: It  is  expected that Network is the same one which  was  passed  to
+          MLPStartTraining() function.  However,  THIS  function  checks  only
+          following:
+          * that number of network inputs is consistent with trainer object
+            settings
+          * that number of network outputs/classes is consistent with  trainer
+            object settings
+          * that number of network weights is the same as number of weights in
+            the network passed to MLPStartTraining() function
+          Exception is thrown when these conditions are violated.
+
+          It is also expected that you do not change state of the  network  on
+          your own - the only party who has right to change network during its
+          training is a trainer object. Any attempt to interfere with  trainer
+          may lead to unpredictable results.
+
+
+      -- ALGLIB --
+         Copyright 23.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool mlpcontinuetraining(mlptrainer s, multilayerperceptron network)
+    {
+    
+        return mlptrain.mlpcontinuetraining(s.innerobj, network.innerobj, null);
+    }
+    
+    public static bool mlpcontinuetraining(mlptrainer s, multilayerperceptron network, alglib.xparams _params)
+    {
+    
+        return mlptrain.mlpcontinuetraining(s.innerobj, network.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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();
+        mlptrain.mlpebagginglm(ensemble.innerobj, xy, npoints, decay, restarts, ref info, rep.innerobj, ooberrors.innerobj, null);
+    }
+    
+    public static void mlpebagginglm(mlpensemble ensemble, double[,] xy, int npoints, double decay, int restarts, out int info, out mlpreport rep, out mlpcvreport ooberrors, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new mlpreport();
+        ooberrors = new mlpcvreport();
+        mlptrain.mlpebagginglm(ensemble.innerobj, xy, npoints, decay, restarts, ref info, rep.innerobj, ooberrors.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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();
+        mlptrain.mlpebagginglbfgs(ensemble.innerobj, xy, npoints, decay, restarts, wstep, maxits, ref info, rep.innerobj, ooberrors.innerobj, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new mlpreport();
+        ooberrors = new mlpcvreport();
+        mlptrain.mlpebagginglbfgs(ensemble.innerobj, xy, npoints, decay, restarts, wstep, maxits, ref info, rep.innerobj, ooberrors.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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();
+        mlptrain.mlpetraines(ensemble.innerobj, xy, npoints, decay, restarts, ref info, rep.innerobj, null);
+    }
+    
+    public static void mlpetraines(mlpensemble ensemble, double[,] xy, int npoints, double decay, int restarts, out int info, out mlpreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new mlpreport();
+        mlptrain.mlpetraines(ensemble.innerobj, xy, npoints, decay, restarts, ref info, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function trains neural network ensemble passed to this function using
+    current dataset and early stopping training algorithm. Each early stopping
+    round performs NRestarts  random  restarts  (thus,  EnsembleSize*NRestarts
+    training rounds is performed in total).
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        S           -   trainer object;
+        Ensemble    -   neural network ensemble. It must have same  number  of
+                        inputs and outputs/classes  as  was  specified  during
+                        creation of the trainer object.
+        NRestarts   -   number of restarts, >=0:
+                        * NRestarts>0 means that specified  number  of  random
+                          restarts are performed during each ES round;
+                        * NRestarts=0 is silently replaced by 1.
+
+    OUTPUT PARAMETERS:
+        Ensemble    -   trained ensemble;
+        Rep         -   it contains all type of errors.
+
+    NOTE: this training method uses BOTH early stopping and weight decay!  So,
+          you should select weight decay before starting training just as  you
+          select it before training "conventional" networks.
+
+    NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(),
+          or  single-point  dataset  was  passed,  ensemble  is filled by zero
+          values.
+
+    NOTE: this method uses sum-of-squares error function for training.
+
+      -- ALGLIB --
+         Copyright 22.08.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mlptrainensemblees(mlptrainer s, mlpensemble ensemble, int nrestarts, out mlpreport rep)
+    {
+        rep = new mlpreport();
+        mlptrain.mlptrainensemblees(s.innerobj, ensemble.innerobj, nrestarts, rep.innerobj, null);
+    }
+    
+    public static void mlptrainensemblees(mlptrainer s, mlpensemble ensemble, int nrestarts, out mlpreport rep, alglib.xparams _params)
+    {
+        rep = new mlpreport();
+        mlptrain.mlptrainensemblees(s.innerobj, ensemble.innerobj, nrestarts, rep.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This structure is a clusterization engine.
+
+    You should not try to access its fields directly.
+    Use ALGLIB functions in order to work with this object.
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public class clusterizerstate : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public clusterizerstate()
+        {
+            _innerobj = new clustering.clusterizerstate();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new clusterizerstate((clustering.clusterizerstate)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private clustering.clusterizerstate _innerobj;
+        public clustering.clusterizerstate innerobj { get { return _innerobj; } }
+        public clusterizerstate(clustering.clusterizerstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This structure  is used to store results of the agglomerative hierarchical
+    clustering (AHC).
+
+    Following information is returned:
+
+    * TerminationType - completion code:
+      * 1   for successful completion of algorithm
+      * -5  inappropriate combination of  clustering  algorithm  and  distance
+            function was used. As for now, it  is  possible  only when  Ward's
+            method is called for dataset with non-Euclidean distance function.
+      In case negative completion code is returned,  other  fields  of  report
+      structure are invalid and should not be used.
+
+    * NPoints contains number of points in the original dataset
+
+    * Z contains information about merges performed  (see below).  Z  contains
+      indexes from the original (unsorted) dataset and it can be used when you
+      need to know what points were merged. However, it is not convenient when
+      you want to build a dendrograd (see below).
+
+    * if  you  want  to  build  dendrogram, you  can use Z, but it is not good
+      option, because Z contains  indexes from  unsorted  dataset.  Dendrogram
+      built from such dataset is likely to have intersections. So, you have to
+      reorder you points before building dendrogram.
+      Permutation which reorders point is returned in P. Another representation
+      of  merges,  which  is  more  convenient for dendorgram construction, is
+      returned in PM.
+
+    * more information on format of Z, P and PM can be found below and in the
+      examples from ALGLIB Reference Manual.
+
+    FORMAL DESCRIPTION OF FIELDS:
+        NPoints         number of points
+        Z               array[NPoints-1,2],  contains   indexes   of  clusters
+                        linked in pairs to  form  clustering  tree.  I-th  row
+                        corresponds to I-th merge:
+                        * Z[I,0] - index of the first cluster to merge
+                        * Z[I,1] - index of the second cluster to merge
+                        * Z[I,0]<Z[I,1]
+                        * clusters are  numbered  from 0 to 2*NPoints-2,  with
+                          indexes from 0 to NPoints-1 corresponding to  points
+                          of the original dataset, and indexes from NPoints to
+                          2*NPoints-2  correspond  to  clusters  generated  by
+                          subsequent  merges  (I-th  row  of Z creates cluster
+                          with index NPoints+I).
+
+                        IMPORTANT: indexes in Z[] are indexes in the ORIGINAL,
+                        unsorted dataset. In addition to  Z algorithm  outputs
+                        permutation which rearranges points in such  way  that
+                        subsequent merges are  performed  on  adjacent  points
+                        (such order is needed if you want to build dendrogram).
+                        However,  indexes  in  Z  are  related  to   original,
+                        unrearranged sequence of points.
+
+        P               array[NPoints], permutation which reorders points  for
+                        dendrogram  construction.  P[i] contains  index of the
+                        position  where  we  should  move  I-th  point  of the
+                        original dataset in order to apply merges PZ/PM.
+
+        PZ              same as Z, but for permutation of points given  by  P.
+                        The  only  thing  which  changed  are  indexes  of the
+                        original points; indexes of clusters remained same.
+
+        MergeDist       array[NPoints-1], contains distances between  clusters
+                        being merged (MergeDist[i] correspond to merge  stored
+                        in Z[i,...]):
+                        * CLINK, SLINK and  average  linkage algorithms report
+                          "raw", unmodified distance metric.
+                        * Ward's   method   reports   weighted   intra-cluster
+                          variance, which is equal to ||Ca-Cb||^2 * Sa*Sb/(Sa+Sb).
+                          Here  A  and  B  are  clusters being merged, Ca is a
+                          center of A, Cb is a center of B, Sa is a size of A,
+                          Sb is a size of B.
+
+        PM              array[NPoints-1,6], another representation of  merges,
+                        which is suited for dendrogram construction. It  deals
+                        with rearranged points (permutation P is applied)  and
+                        represents merges in a form which different  from  one
+                        used by Z.
+                        For each I from 0 to NPoints-2, I-th row of PM represents
+                        merge performed on two clusters C0 and C1. Here:
+                        * C0 contains points with indexes PM[I,0]...PM[I,1]
+                        * C1 contains points with indexes PM[I,2]...PM[I,3]
+                        * indexes stored in PM are given for dataset sorted
+                          according to permutation P
+                        * PM[I,1]=PM[I,2]-1 (only adjacent clusters are merged)
+                        * PM[I,0]<=PM[I,1], PM[I,2]<=PM[I,3], i.e. both
+                          clusters contain at least one point
+                        * heights of "subdendrograms" corresponding  to  C0/C1
+                          are stored in PM[I,4]  and  PM[I,5].  Subdendrograms
+                          corresponding   to   single-point   clusters    have
+                          height=0. Dendrogram of the merge result has  height
+                          H=max(H0,H1)+1.
+
+    NOTE: there is one-to-one correspondence between merges described by Z and
+          PM. I-th row of Z describes same merge of clusters as I-th row of PM,
+          with "left" cluster from Z corresponding to the "left" one from PM.
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public class ahcreport : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+        public int npoints { get { return _innerobj.npoints; } set { _innerobj.npoints = value; } }
+        public int[] p { get { return _innerobj.p; } set { _innerobj.p = value; } }
+        public int[,] z { get { return _innerobj.z; } set { _innerobj.z = value; } }
+        public int[,] pz { get { return _innerobj.pz; } set { _innerobj.pz = value; } }
+        public int[,] pm { get { return _innerobj.pm; } set { _innerobj.pm = value; } }
+        public double[] mergedist { get { return _innerobj.mergedist; } set { _innerobj.mergedist = value; } }
+    
+        public ahcreport()
+        {
+            _innerobj = new clustering.ahcreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new ahcreport((clustering.ahcreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private clustering.ahcreport _innerobj;
+        public clustering.ahcreport innerobj { get { return _innerobj; } }
+        public ahcreport(clustering.ahcreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This  structure   is  used  to  store  results of the  k-means  clustering
+    algorithm.
+
+    Following information is always returned:
+    * NPoints contains number of points in the original dataset
+    * TerminationType contains completion code, negative on failure, positive
+      on success
+    * K contains number of clusters
+
+    For positive TerminationType we return:
+    * NFeatures contains number of variables in the original dataset
+    * C, which contains centers found by algorithm
+    * CIdx, which maps points of the original dataset to clusters
+
+    FORMAL DESCRIPTION OF FIELDS:
+        NPoints         number of points, >=0
+        NFeatures       number of variables, >=1
+        TerminationType completion code:
+                        * -5 if  distance  type  is  anything  different  from
+                             Euclidean metric
+                        * -3 for degenerate dataset: a) less  than  K  distinct
+                             points, b) K=0 for non-empty dataset.
+                        * +1 for successful completion
+        K               number of clusters
+        C               array[K,NFeatures], rows of the array store centers
+        CIdx            array[NPoints], which contains cluster indexes
+        IterationsCount actual number of iterations performed by clusterizer.
+                        If algorithm performed more than one random restart,
+                        total number of iterations is returned.
+        Energy          merit function, "energy", sum  of  squared  deviations
+                        from cluster centers
+
+      -- ALGLIB --
+         Copyright 27.11.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public class kmeansreport : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public int npoints { get { return _innerobj.npoints; } set { _innerobj.npoints = value; } }
+        public int nfeatures { get { return _innerobj.nfeatures; } set { _innerobj.nfeatures = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+        public int iterationscount { get { return _innerobj.iterationscount; } set { _innerobj.iterationscount = value; } }
+        public double energy { get { return _innerobj.energy; } set { _innerobj.energy = value; } }
+        public int k { get { return _innerobj.k; } set { _innerobj.k = value; } }
+        public double[,] c { get { return _innerobj.c; } set { _innerobj.c = value; } }
+        public int[] cidx { get { return _innerobj.cidx; } set { _innerobj.cidx = value; } }
+    
+        public kmeansreport()
+        {
+            _innerobj = new clustering.kmeansreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new kmeansreport((clustering.kmeansreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private clustering.kmeansreport _innerobj;
+        public clustering.kmeansreport innerobj { get { return _innerobj; } }
+        public kmeansreport(clustering.kmeansreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+    
+    /*************************************************************************
+    This function initializes clusterizer object. Newly initialized object  is
+    empty, i.e. it does not contain dataset. You should use it as follows:
+    1. creation
+    2. dataset is added with ClusterizerSetPoints()
+    3. additional parameters are set
+    3. clusterization is performed with one of the clustering functions
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizercreate(out clusterizerstate s)
+    {
+        s = new clusterizerstate();
+        clustering.clusterizercreate(s.innerobj, null);
+    }
+    
+    public static void clusterizercreate(out clusterizerstate s, alglib.xparams _params)
+    {
+        s = new clusterizerstate();
+        clustering.clusterizercreate(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function adds dataset to the clusterizer structure.
+
+    This function overrides all previous calls  of  ClusterizerSetPoints()  or
+    ClusterizerSetDistances().
+
+    INPUT PARAMETERS:
+        S       -   clusterizer state, initialized by ClusterizerCreate()
+        XY      -   array[NPoints,NFeatures], dataset
+        NPoints -   number of points, >=0
+        NFeatures-  number of features, >=1
+        DistType-   distance function:
+                    *  0    Chebyshev distance  (L-inf norm)
+                    *  1    city block distance (L1 norm)
+                    *  2    Euclidean distance  (L2 norm), non-squared
+                    * 10    Pearson correlation:
+                            dist(a,b) = 1-corr(a,b)
+                    * 11    Absolute Pearson correlation:
+                            dist(a,b) = 1-|corr(a,b)|
+                    * 12    Uncentered Pearson correlation (cosine of the angle):
+                            dist(a,b) = a'*b/(|a|*|b|)
+                    * 13    Absolute uncentered Pearson correlation
+                            dist(a,b) = |a'*b|/(|a|*|b|)
+                    * 20    Spearman rank correlation:
+                            dist(a,b) = 1-rankcorr(a,b)
+                    * 21    Absolute Spearman rank correlation
+                            dist(a,b) = 1-|rankcorr(a,b)|
+
+    NOTE 1: different distance functions have different performance penalty:
+            * Euclidean or Pearson correlation distances are the fastest ones
+            * Spearman correlation distance function is a bit slower
+            * city block and Chebyshev distances are order of magnitude slower
+
+            The reason behing difference in performance is that correlation-based
+            distance functions are computed using optimized linear algebra kernels,
+            while Chebyshev and city block distance functions are computed using
+            simple nested loops with two branches at each iteration.
+
+    NOTE 2: different clustering algorithms have different limitations:
+            * agglomerative hierarchical clustering algorithms may be used with
+              any kind of distance metric
+            * k-means++ clustering algorithm may be used only  with  Euclidean
+              distance function
+            Thus, list of specific clustering algorithms you may  use  depends
+            on distance function you specify when you set your dataset.
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizersetpoints(clusterizerstate s, double[,] xy, int npoints, int nfeatures, int disttype)
+    {
+    
+        clustering.clusterizersetpoints(s.innerobj, xy, npoints, nfeatures, disttype, null);
+    }
+    
+    public static void clusterizersetpoints(clusterizerstate s, double[,] xy, int npoints, int nfeatures, int disttype, alglib.xparams _params)
+    {
+    
+        clustering.clusterizersetpoints(s.innerobj, xy, npoints, nfeatures, disttype, _params);
+    }
+            
+    public static void clusterizersetpoints(clusterizerstate s, double[,] xy, int disttype)
+    {
+        int npoints;
+        int nfeatures;
+    
+    
+        npoints = ap.rows(xy);
+        nfeatures = ap.cols(xy);
+        clustering.clusterizersetpoints(s.innerobj, xy, npoints, nfeatures, disttype, null);
+    
+        return;
+    }
+            
+    public static void clusterizersetpoints(clusterizerstate s, double[,] xy, int disttype, alglib.xparams _params)
+    {
+        int npoints;
+        int nfeatures;
+    
+    
+        npoints = ap.rows(xy);
+        nfeatures = ap.cols(xy);
+        clustering.clusterizersetpoints(s.innerobj, xy, npoints, nfeatures, disttype, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function adds dataset given by distance  matrix  to  the  clusterizer
+    structure. It is important that dataset is not  given  explicitly  -  only
+    distance matrix is given.
+
+    This function overrides all previous calls  of  ClusterizerSetPoints()  or
+    ClusterizerSetDistances().
+
+    INPUT PARAMETERS:
+        S       -   clusterizer state, initialized by ClusterizerCreate()
+        D       -   array[NPoints,NPoints], distance matrix given by its upper
+                    or lower triangle (main diagonal is  ignored  because  its
+                    entries are expected to be zero).
+        NPoints -   number of points
+        IsUpper -   whether upper or lower triangle of D is given.
+
+    NOTE 1: different clustering algorithms have different limitations:
+            * agglomerative hierarchical clustering algorithms may be used with
+              any kind of distance metric, including one  which  is  given  by
+              distance matrix
+            * k-means++ clustering algorithm may be used only  with  Euclidean
+              distance function and explicitly given points - it  can  not  be
+              used with dataset given by distance matrix
+            Thus, if you call this function, you will be unable to use k-means
+            clustering algorithm to process your problem.
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizersetdistances(clusterizerstate s, double[,] d, int npoints, bool isupper)
+    {
+    
+        clustering.clusterizersetdistances(s.innerobj, d, npoints, isupper, null);
+    }
+    
+    public static void clusterizersetdistances(clusterizerstate s, double[,] d, int npoints, bool isupper, alglib.xparams _params)
+    {
+    
+        clustering.clusterizersetdistances(s.innerobj, d, npoints, isupper, _params);
+    }
+            
+    public static void clusterizersetdistances(clusterizerstate s, double[,] d, bool isupper)
+    {
+        int npoints;
+        if( (ap.rows(d)!=ap.cols(d)))
+            throw new alglibexception("Error while calling 'clusterizersetdistances': looks like one of arguments has wrong size");
+    
+        npoints = ap.rows(d);
+        clustering.clusterizersetdistances(s.innerobj, d, npoints, isupper, null);
+    
+        return;
+    }
+            
+    public static void clusterizersetdistances(clusterizerstate s, double[,] d, bool isupper, alglib.xparams _params)
+    {
+        int npoints;
+        if( (ap.rows(d)!=ap.cols(d)))
+            throw new alglibexception("Error while calling 'clusterizersetdistances': looks like one of arguments has wrong size");
+    
+        npoints = ap.rows(d);
+        clustering.clusterizersetdistances(s.innerobj, d, npoints, isupper, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function sets agglomerative hierarchical clustering algorithm
+
+    INPUT PARAMETERS:
+        S       -   clusterizer state, initialized by ClusterizerCreate()
+        Algo    -   algorithm type:
+                    * 0     complete linkage (default algorithm)
+                    * 1     single linkage
+                    * 2     unweighted average linkage
+                    * 3     weighted average linkage
+                    * 4     Ward's method
+
+    NOTE: Ward's method works correctly only with Euclidean  distance,  that's
+          why algorithm will return negative termination  code  (failure)  for
+          any other distance type.
+
+          It is possible, however,  to  use  this  method  with  user-supplied
+          distance matrix. It  is  your  responsibility  to pass one which was
+          calculated with Euclidean distance function.
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizersetahcalgo(clusterizerstate s, int algo)
+    {
+    
+        clustering.clusterizersetahcalgo(s.innerobj, algo, null);
+    }
+    
+    public static void clusterizersetahcalgo(clusterizerstate s, int algo, alglib.xparams _params)
+    {
+    
+        clustering.clusterizersetahcalgo(s.innerobj, algo, _params);
+    }
+    
+    /*************************************************************************
+    This  function  sets k-means properties:  number  of  restarts and maximum
+    number of iterations per one run.
+
+    INPUT PARAMETERS:
+        S       -   clusterizer state, initialized by ClusterizerCreate()
+        Restarts-   restarts count, >=1.
+                    k-means++ algorithm performs several restarts and  chooses
+                    best set of centers (one with minimum squared distance).
+        MaxIts  -   maximum number of k-means iterations performed during  one
+                    run. >=0, zero value means that algorithm performs unlimited
+                    number of iterations.
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizersetkmeanslimits(clusterizerstate s, int restarts, int maxits)
+    {
+    
+        clustering.clusterizersetkmeanslimits(s.innerobj, restarts, maxits, null);
+    }
+    
+    public static void clusterizersetkmeanslimits(clusterizerstate s, int restarts, int maxits, alglib.xparams _params)
+    {
+    
+        clustering.clusterizersetkmeanslimits(s.innerobj, restarts, maxits, _params);
+    }
+    
+    /*************************************************************************
+    This function sets k-means  initialization  algorithm.  Several  different
+    algorithms can be chosen, including k-means++.
+
+    INPUT PARAMETERS:
+        S       -   clusterizer state, initialized by ClusterizerCreate()
+        InitAlgo-   initialization algorithm:
+                    * 0  automatic selection ( different  versions  of  ALGLIB
+                         may select different algorithms)
+                    * 1  random initialization
+                    * 2  k-means++ initialization  (best  quality  of  initial
+                         centers, but long  non-parallelizable  initialization
+                         phase with bad cache locality)
+                    * 3  "fast-greedy"  algorithm  with  efficient,  easy   to
+                         parallelize initialization. Quality of initial centers
+                         is  somewhat  worse  than  that  of  k-means++.  This
+                         algorithm is a default one in the current version  of
+                         ALGLIB.
+                    *-1  "debug" algorithm which always selects first  K  rows
+                         of dataset; this algorithm is used for debug purposes
+                         only. Do not use it in the industrial code!
+
+      -- ALGLIB --
+         Copyright 21.01.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizersetkmeansinit(clusterizerstate s, int initalgo)
+    {
+    
+        clustering.clusterizersetkmeansinit(s.innerobj, initalgo, null);
+    }
+    
+    public static void clusterizersetkmeansinit(clusterizerstate s, int initalgo, alglib.xparams _params)
+    {
+    
+        clustering.clusterizersetkmeansinit(s.innerobj, initalgo, _params);
+    }
+    
+    /*************************************************************************
+    This  function  sets  seed  which  is  used to initialize internal RNG. By
+    default, deterministic seed is used - same for each run of clusterizer. If
+    you specify non-deterministic  seed  value,  then  some  algorithms  which
+    depend on random initialization (in current version: k-means)  may  return
+    slightly different results after each run.
+
+    INPUT PARAMETERS:
+        S       -   clusterizer state, initialized by ClusterizerCreate()
+        Seed    -   seed:
+                    * positive values = use deterministic seed for each run of
+                      algorithms which depend on random initialization
+                    * zero or negative values = use non-deterministic seed
+
+      -- ALGLIB --
+         Copyright 08.06.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizersetseed(clusterizerstate s, int seed)
+    {
+    
+        clustering.clusterizersetseed(s.innerobj, seed, null);
+    }
+    
+    public static void clusterizersetseed(clusterizerstate s, int seed, alglib.xparams _params)
+    {
+    
+        clustering.clusterizersetseed(s.innerobj, seed, _params);
+    }
+    
+    /*************************************************************************
+    This function performs agglomerative hierarchical clustering
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    NOTE: Agglomerative  hierarchical  clustering  algorithm  has two  phases:
+          distance matrix calculation and clustering  itself. Only first phase
+          (distance matrix  calculation)  is  accelerated  by  Intel  MKL  and
+          multithreading. Thus, acceleration is significant only for medium or
+          high-dimensional problems.
+
+          Although activating multithreading gives some speedup  over  single-
+          threaded execution, you  should  not  expect  nearly-linear  scaling
+          with respect to cores count.
+
+    INPUT PARAMETERS:
+        S       -   clusterizer state, initialized by ClusterizerCreate()
+
+    OUTPUT PARAMETERS:
+        Rep     -   clustering results; see description of AHCReport
+                    structure for more information.
+
+    NOTE 1: hierarchical clustering algorithms require large amounts of memory.
+            In particular, this implementation needs  sizeof(double)*NPoints^2
+            bytes, which are used to store distance matrix. In  case  we  work
+            with user-supplied matrix, this amount is multiplied by 2 (we have
+            to store original matrix and to work with its copy).
+
+            For example, problem with 10000 points  would require 800M of RAM,
+            even when working in a 1-dimensional space.
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizerrunahc(clusterizerstate s, out ahcreport rep)
+    {
+        rep = new ahcreport();
+        clustering.clusterizerrunahc(s.innerobj, rep.innerobj, null);
+    }
+    
+    public static void clusterizerrunahc(clusterizerstate s, out ahcreport rep, alglib.xparams _params)
+    {
+        rep = new ahcreport();
+        clustering.clusterizerrunahc(s.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function performs clustering by k-means++ algorithm.
+
+    You may change algorithm properties by calling:
+    * ClusterizerSetKMeansLimits() to change number of restarts or iterations
+    * ClusterizerSetKMeansInit() to change initialization algorithm
+
+    By  default,  one  restart  and  unlimited number of iterations are  used.
+    Initialization algorithm is chosen automatically.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    NOTE: k-means clustering  algorithm has two  phases:  selection of initial
+          centers and clustering  itself.  ALGLIB  parallelizes  both  phases.
+          Parallel version is optimized for the following  scenario: medium or
+          high-dimensional problem (8 or more dimensions) with large number of
+          points and clusters. However, some speed-up  can  be  obtained  even
+          when assumptions above are violated.
+
+    INPUT PARAMETERS:
+        S       -   clusterizer state, initialized by ClusterizerCreate()
+        K       -   number of clusters, K>=0.
+                    K  can  be  zero only when algorithm is called  for  empty
+                    dataset,  in   this   case   completion  code  is  set  to
+                    success (+1).
+                    If  K=0  and  dataset  size  is  non-zero,  we   can   not
+                    meaningfully assign points to some center  (there  are  no
+                    centers because K=0) and  return  -3  as  completion  code
+                    (failure).
+
+    OUTPUT PARAMETERS:
+        Rep     -   clustering results; see description of KMeansReport
+                    structure for more information.
+
+    NOTE 1: k-means  clustering  can  be  performed  only  for  datasets  with
+            Euclidean  distance  function.  Algorithm  will  return   negative
+            completion code in Rep.TerminationType in case dataset  was  added
+            to clusterizer with DistType other than Euclidean (or dataset  was
+            specified by distance matrix instead of explicitly given points).
+
+    NOTE 2: by default, k-means uses non-deterministic seed to initialize  RNG
+            which is used to select initial centers. As  result,  each  run of
+            algorithm may return different values. If you  need  deterministic
+            behavior, use ClusterizerSetSeed() function.
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizerrunkmeans(clusterizerstate s, int k, out kmeansreport rep)
+    {
+        rep = new kmeansreport();
+        clustering.clusterizerrunkmeans(s.innerobj, k, rep.innerobj, null);
+    }
+    
+    public static void clusterizerrunkmeans(clusterizerstate s, int k, out kmeansreport rep, alglib.xparams _params)
+    {
+        rep = new kmeansreport();
+        clustering.clusterizerrunkmeans(s.innerobj, k, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function returns distance matrix for dataset
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        XY      -   array[NPoints,NFeatures], dataset
+        NPoints -   number of points, >=0
+        NFeatures-  number of features, >=1
+        DistType-   distance function:
+                    *  0    Chebyshev distance  (L-inf norm)
+                    *  1    city block distance (L1 norm)
+                    *  2    Euclidean distance  (L2 norm, non-squared)
+                    * 10    Pearson correlation:
+                            dist(a,b) = 1-corr(a,b)
+                    * 11    Absolute Pearson correlation:
+                            dist(a,b) = 1-|corr(a,b)|
+                    * 12    Uncentered Pearson correlation (cosine of the angle):
+                            dist(a,b) = a'*b/(|a|*|b|)
+                    * 13    Absolute uncentered Pearson correlation
+                            dist(a,b) = |a'*b|/(|a|*|b|)
+                    * 20    Spearman rank correlation:
+                            dist(a,b) = 1-rankcorr(a,b)
+                    * 21    Absolute Spearman rank correlation
+                            dist(a,b) = 1-|rankcorr(a,b)|
+
+    OUTPUT PARAMETERS:
+        D       -   array[NPoints,NPoints], distance matrix
+                    (full matrix is returned, with lower and upper triangles)
+
+    NOTE:  different distance functions have different performance penalty:
+           * Euclidean or Pearson correlation distances are the fastest ones
+           * Spearman correlation distance function is a bit slower
+           * city block and Chebyshev distances are order of magnitude slower
+
+           The reason behing difference in performance is that correlation-based
+           distance functions are computed using optimized linear algebra kernels,
+           while Chebyshev and city block distance functions are computed using
+           simple nested loops with two branches at each iteration.
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizergetdistances(double[,] xy, int npoints, int nfeatures, int disttype, out double[,] d)
+    {
+        d = new double[0,0];
+        clustering.clusterizergetdistances(xy, npoints, nfeatures, disttype, ref d, null);
+    }
+    
+    public static void clusterizergetdistances(double[,] xy, int npoints, int nfeatures, int disttype, out double[,] d, alglib.xparams _params)
+    {
+        d = new double[0,0];
+        clustering.clusterizergetdistances(xy, npoints, nfeatures, disttype, ref d, _params);
+    }
+    
+    /*************************************************************************
+    This function takes as input clusterization report Rep,  desired  clusters
+    count K, and builds top K clusters from hierarchical clusterization  tree.
+    It returns assignment of points to clusters (array of cluster indexes).
+
+    INPUT PARAMETERS:
+        Rep     -   report from ClusterizerRunAHC() performed on XY
+        K       -   desired number of clusters, 1<=K<=NPoints.
+                    K can be zero only when NPoints=0.
+
+    OUTPUT PARAMETERS:
+        CIdx    -   array[NPoints], I-th element contains cluster index  (from
+                    0 to K-1) for I-th point of the dataset.
+        CZ      -   array[K]. This array allows  to  convert  cluster  indexes
+                    returned by this function to indexes used by  Rep.Z.  J-th
+                    cluster returned by this function corresponds to  CZ[J]-th
+                    cluster stored in Rep.Z/PZ/PM.
+                    It is guaranteed that CZ[I]<CZ[I+1].
+
+    NOTE: K clusters built by this subroutine are assumed to have no hierarchy.
+          Although  they  were  obtained  by  manipulation with top K nodes of
+          dendrogram  (i.e.  hierarchical  decomposition  of  dataset),   this
+          function does not return information about hierarchy.  Each  of  the
+          clusters stand on its own.
+
+    NOTE: Cluster indexes returned by this function  does  not  correspond  to
+          indexes returned in Rep.Z/PZ/PM. Either you work  with  hierarchical
+          representation of the dataset (dendrogram), or you work with  "flat"
+          representation returned by this function.  Each  of  representations
+          has its own clusters indexing system (former uses [0, 2*NPoints-2]),
+          while latter uses [0..K-1]), although  it  is  possible  to  perform
+          conversion from one system to another by means of CZ array, returned
+          by this function, which allows you to convert indexes stored in CIdx
+          to the numeration system used by Rep.Z.
+
+    NOTE: this subroutine is optimized for moderate values of K. Say, for  K=5
+          it will perform many times faster than  for  K=100.  Its  worst-case
+          performance is O(N*K), although in average case  it  perform  better
+          (up to O(N*log(K))).
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizergetkclusters(ahcreport rep, int k, out int[] cidx, out int[] cz)
+    {
+        cidx = new int[0];
+        cz = new int[0];
+        clustering.clusterizergetkclusters(rep.innerobj, k, ref cidx, ref cz, null);
+    }
+    
+    public static void clusterizergetkclusters(ahcreport rep, int k, out int[] cidx, out int[] cz, alglib.xparams _params)
+    {
+        cidx = new int[0];
+        cz = new int[0];
+        clustering.clusterizergetkclusters(rep.innerobj, k, ref cidx, ref cz, _params);
+    }
+    
+    /*************************************************************************
+    This  function  accepts  AHC  report  Rep,  desired  minimum  intercluster
+    distance and returns top clusters from  hierarchical  clusterization  tree
+    which are separated by distance R or HIGHER.
+
+    It returns assignment of points to clusters (array of cluster indexes).
+
+    There is one more function with similar name - ClusterizerSeparatedByCorr,
+    which returns clusters with intercluster correlation equal to R  or  LOWER
+    (note: higher for distance, lower for correlation).
+
+    INPUT PARAMETERS:
+        Rep     -   report from ClusterizerRunAHC() performed on XY
+        R       -   desired minimum intercluster distance, R>=0
+
+    OUTPUT PARAMETERS:
+        K       -   number of clusters, 1<=K<=NPoints
+        CIdx    -   array[NPoints], I-th element contains cluster index  (from
+                    0 to K-1) for I-th point of the dataset.
+        CZ      -   array[K]. This array allows  to  convert  cluster  indexes
+                    returned by this function to indexes used by  Rep.Z.  J-th
+                    cluster returned by this function corresponds to  CZ[J]-th
+                    cluster stored in Rep.Z/PZ/PM.
+                    It is guaranteed that CZ[I]<CZ[I+1].
+
+    NOTE: K clusters built by this subroutine are assumed to have no hierarchy.
+          Although  they  were  obtained  by  manipulation with top K nodes of
+          dendrogram  (i.e.  hierarchical  decomposition  of  dataset),   this
+          function does not return information about hierarchy.  Each  of  the
+          clusters stand on its own.
+
+    NOTE: Cluster indexes returned by this function  does  not  correspond  to
+          indexes returned in Rep.Z/PZ/PM. Either you work  with  hierarchical
+          representation of the dataset (dendrogram), or you work with  "flat"
+          representation returned by this function.  Each  of  representations
+          has its own clusters indexing system (former uses [0, 2*NPoints-2]),
+          while latter uses [0..K-1]), although  it  is  possible  to  perform
+          conversion from one system to another by means of CZ array, returned
+          by this function, which allows you to convert indexes stored in CIdx
+          to the numeration system used by Rep.Z.
+
+    NOTE: this subroutine is optimized for moderate values of K. Say, for  K=5
+          it will perform many times faster than  for  K=100.  Its  worst-case
+          performance is O(N*K), although in average case  it  perform  better
+          (up to O(N*log(K))).
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizerseparatedbydist(ahcreport rep, double r, out int k, out int[] cidx, out int[] cz)
+    {
+        k = 0;
+        cidx = new int[0];
+        cz = new int[0];
+        clustering.clusterizerseparatedbydist(rep.innerobj, r, ref k, ref cidx, ref cz, null);
+    }
+    
+    public static void clusterizerseparatedbydist(ahcreport rep, double r, out int k, out int[] cidx, out int[] cz, alglib.xparams _params)
+    {
+        k = 0;
+        cidx = new int[0];
+        cz = new int[0];
+        clustering.clusterizerseparatedbydist(rep.innerobj, r, ref k, ref cidx, ref cz, _params);
+    }
+    
+    /*************************************************************************
+    This  function  accepts  AHC  report  Rep,  desired  maximum  intercluster
+    correlation and returns top clusters from hierarchical clusterization tree
+    which are separated by correlation R or LOWER.
+
+    It returns assignment of points to clusters (array of cluster indexes).
+
+    There is one more function with similar name - ClusterizerSeparatedByDist,
+    which returns clusters with intercluster distance equal  to  R  or  HIGHER
+    (note: higher for distance, lower for correlation).
+
+    INPUT PARAMETERS:
+        Rep     -   report from ClusterizerRunAHC() performed on XY
+        R       -   desired maximum intercluster correlation, -1<=R<=+1
+
+    OUTPUT PARAMETERS:
+        K       -   number of clusters, 1<=K<=NPoints
+        CIdx    -   array[NPoints], I-th element contains cluster index  (from
+                    0 to K-1) for I-th point of the dataset.
+        CZ      -   array[K]. This array allows  to  convert  cluster  indexes
+                    returned by this function to indexes used by  Rep.Z.  J-th
+                    cluster returned by this function corresponds to  CZ[J]-th
+                    cluster stored in Rep.Z/PZ/PM.
+                    It is guaranteed that CZ[I]<CZ[I+1].
+
+    NOTE: K clusters built by this subroutine are assumed to have no hierarchy.
+          Although  they  were  obtained  by  manipulation with top K nodes of
+          dendrogram  (i.e.  hierarchical  decomposition  of  dataset),   this
+          function does not return information about hierarchy.  Each  of  the
+          clusters stand on its own.
+
+    NOTE: Cluster indexes returned by this function  does  not  correspond  to
+          indexes returned in Rep.Z/PZ/PM. Either you work  with  hierarchical
+          representation of the dataset (dendrogram), or you work with  "flat"
+          representation returned by this function.  Each  of  representations
+          has its own clusters indexing system (former uses [0, 2*NPoints-2]),
+          while latter uses [0..K-1]), although  it  is  possible  to  perform
+          conversion from one system to another by means of CZ array, returned
+          by this function, which allows you to convert indexes stored in CIdx
+          to the numeration system used by Rep.Z.
+
+    NOTE: this subroutine is optimized for moderate values of K. Say, for  K=5
+          it will perform many times faster than  for  K=100.  Its  worst-case
+          performance is O(N*K), although in average case  it  perform  better
+          (up to O(N*log(K))).
+
+      -- ALGLIB --
+         Copyright 10.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void clusterizerseparatedbycorr(ahcreport rep, double r, out int k, out int[] cidx, out int[] cz)
+    {
+        k = 0;
+        cidx = new int[0];
+        cz = new int[0];
+        clustering.clusterizerseparatedbycorr(rep.innerobj, r, ref k, ref cidx, ref cz, null);
+    }
+    
+    public static void clusterizerseparatedbycorr(ahcreport rep, double r, out int k, out int[] cidx, out int[] cz, alglib.xparams _params)
+    {
+        k = 0;
+        cidx = new int[0];
+        cz = new int[0];
+        clustering.clusterizerseparatedbycorr(rep.innerobj, r, ref k, ref cidx, ref cz, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    A random forest (decision forest) builder object.
+
+    Used to store dataset and specify random forest training algorithm settings.
+    *************************************************************************/
+    public class decisionforestbuilder : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public decisionforestbuilder()
+        {
+            _innerobj = new dforest.decisionforestbuilder();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new decisionforestbuilder((dforest.decisionforestbuilder)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private dforest.decisionforestbuilder _innerobj;
+        public dforest.decisionforestbuilder innerobj { get { return _innerobj; } }
+        public decisionforestbuilder(dforest.decisionforestbuilder obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    Buffer object which is used to perform  various  requests  (usually  model
+    inference) in the multithreaded mode (multiple threads working  with  same
+    DF object).
+
+    This object should be created with DFCreateBuffer().
+    *************************************************************************/
+    public class decisionforestbuffer : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public decisionforestbuffer()
+        {
+            _innerobj = new dforest.decisionforestbuffer();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new decisionforestbuffer((dforest.decisionforestbuffer)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private dforest.decisionforestbuffer _innerobj;
+        public dforest.decisionforestbuffer innerobj { get { return _innerobj; } }
+        public decisionforestbuffer(dforest.decisionforestbuffer obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    Decision forest (random forest) model.
+    *************************************************************************/
+    public class decisionforest : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public decisionforest()
+        {
+            _innerobj = new dforest.decisionforest();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new decisionforest((dforest.decisionforest)_innerobj.make_copy());
+        }
+    
+        //
+        // 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;
+        }
+    }
+
+
+    /*************************************************************************
+    Decision forest training report.
+
+    Following fields store training set errors:
+    * relclserror       -   fraction of misclassified cases, [0,1]
+    * avgce             -   average cross-entropy in bits per symbol
+    * rmserror          -   root-mean-square error
+    * avgerror          -   average error
+    * avgrelerror       -   average relative error
+
+    Out-of-bag estimates are stored in fields with same names, but "oob" prefix.
+
+    For classification problems:
+    * RMS, AVG and AVGREL errors are calculated for posterior probabilities
+
+    For regression problems:
+    * RELCLS and AVGCE errors are zero
+    *************************************************************************/
+    public class dfreport : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new dfreport((dforest.dfreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+        s.sstart_str();
+        dforest.dfserialize(s, obj.innerobj, null);
+        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, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to stream.
+    
+    Data stream generated by this function is same as  string  representation
+    generated  by  string  version  of  serializer - alphanumeric characters,
+    dots, underscores, minus signs, which are grouped into words separated by
+    spaces and CR+LF.
+    
+    We recommend you to read comments on string version of serializer to find
+    out more about serialization of AlGLIB objects.
+    *************************************************************************/
+    public static void dfserialize(decisionforest obj, System.IO.Stream stream_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        dforest.dfalloc(s, obj.innerobj, null);
+        s.sstart_stream(stream_out);
+        dforest.dfserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from stream.
+    *************************************************************************/
+    public static void dfunserialize(System.IO.Stream stream_in, out decisionforest obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new decisionforest();
+        s.ustart_stream(stream_in);
+        dforest.dfunserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+    
+    /*************************************************************************
+    This function creates buffer  structure  which  can  be  used  to  perform
+    parallel inference requests.
+
+    DF subpackage  provides two sets of computing functions - ones  which  use
+    internal buffer of DF model  (these  functions are single-threaded because
+    they use same buffer, which can not  shared  between  threads),  and  ones
+    which use external buffer.
+
+    This function is used to initialize external buffer.
+
+    INPUT PARAMETERS
+        Model       -   DF model which is associated with newly created buffer
+
+    OUTPUT PARAMETERS
+        Buf         -   external buffer.
+
+
+    IMPORTANT: buffer object should be used only with model which was used  to
+               initialize buffer. Any attempt to  use  buffer  with  different
+               object is dangerous - you  may   get  integrity  check  failure
+               (exception) because sizes of internal  arrays  do  not  fit  to
+               dimensions of the model structure.
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfcreatebuffer(decisionforest model, out decisionforestbuffer buf)
+    {
+        buf = new decisionforestbuffer();
+        dforest.dfcreatebuffer(model.innerobj, buf.innerobj, null);
+    }
+    
+    public static void dfcreatebuffer(decisionforest model, out decisionforestbuffer buf, alglib.xparams _params)
+    {
+        buf = new decisionforestbuffer();
+        dforest.dfcreatebuffer(model.innerobj, buf.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine creates DecisionForestBuilder  object  which  is  used  to
+    train random forests.
+
+    By default, new builder stores empty dataset and some  reasonable  default
+    settings. At the very least, you should specify dataset prior to  building
+    decision forest. You can also tweak settings of  the  forest  construction
+    algorithm (recommended, although default setting should work well).
+
+    Following actions are mandatory:
+    * calling dfbuildersetdataset() to specify dataset
+    * calling dfbuilderbuildrandomforest() to build random forest using current
+      dataset and default settings
+
+    Additionally, you may call:
+    * dfbuildersetrndvars() or dfbuildersetrndvarsratio() to specify number of
+      variables randomly chosen for each split
+    * dfbuildersetsubsampleratio() to specify fraction of the dataset randomly
+      subsampled to build each tree
+    * dfbuildersetseed() to control random seed chosen for tree construction
+
+    INPUT PARAMETERS:
+        none
+
+    OUTPUT PARAMETERS:
+        S           -   decision forest builder
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfbuildercreate(out decisionforestbuilder s)
+    {
+        s = new decisionforestbuilder();
+        dforest.dfbuildercreate(s.innerobj, null);
+    }
+    
+    public static void dfbuildercreate(out decisionforestbuilder s, alglib.xparams _params)
+    {
+        s = new decisionforestbuilder();
+        dforest.dfbuildercreate(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine adds dense dataset to the internal storage of the  builder
+    object. Specifying your dataset in the dense format means that  the  dense
+    version of the forest construction algorithm will be invoked.
+
+    INPUT PARAMETERS:
+        S           -   decision forest builder object
+        XY          -   array[NPoints,NVars+1] (minimum size; actual size  can
+                        be larger, only leading part is used anyway), dataset:
+                        * first NVars elements of each row store values of the
+                          independent variables
+                        * last  column  store class number (in 0...NClasses-1)
+                          or real value of the dependent variable
+        NPoints     -   number of rows in the dataset, NPoints>=1
+        NVars       -   number of independent variables, NVars>=1
+        NClasses    -   indicates type of the problem being solved:
+                        * NClasses>=2 means  that  classification  problem  is
+                          solved  (last  column  of  the  dataset stores class
+                          number)
+                        * NClasses=1 means that regression problem  is  solved
+                          (last column of the dataset stores variable value)
+
+    OUTPUT PARAMETERS:
+        S           -   decision forest builder
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfbuildersetdataset(decisionforestbuilder s, double[,] xy, int npoints, int nvars, int nclasses)
+    {
+    
+        dforest.dfbuildersetdataset(s.innerobj, xy, npoints, nvars, nclasses, null);
+    }
+    
+    public static void dfbuildersetdataset(decisionforestbuilder s, double[,] xy, int npoints, int nvars, int nclasses, alglib.xparams _params)
+    {
+    
+        dforest.dfbuildersetdataset(s.innerobj, xy, npoints, nvars, nclasses, _params);
+    }
+    
+    /*************************************************************************
+    This function sets number of variables (in [1,NVars] range) used by random
+    forest construction algorithm.
+
+    The default option is to use roughly sqrt(NVars) variables.
+
+    INPUT PARAMETERS:
+        S           -   decision forest builder object
+        RndVars     -   number of randomly selected variables; values  outside
+                        of [1,NVars] range are silently clipped.
+
+    OUTPUT PARAMETERS:
+        S           -   decision forest builder
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfbuildersetrndvars(decisionforestbuilder s, int rndvars)
+    {
+    
+        dforest.dfbuildersetrndvars(s.innerobj, rndvars, null);
+    }
+    
+    public static void dfbuildersetrndvars(decisionforestbuilder s, int rndvars, alglib.xparams _params)
+    {
+    
+        dforest.dfbuildersetrndvars(s.innerobj, rndvars, _params);
+    }
+    
+    /*************************************************************************
+    This function sets number of variables used by random forest  construction
+    algorithm as a fraction of total variable count (0,1) range.
+
+    The default option is to use roughly sqrt(NVars) variables.
+
+    INPUT PARAMETERS:
+        S           -   decision forest builder object
+        F           -   round(NVars*F) variables are selected
+
+    OUTPUT PARAMETERS:
+        S           -   decision forest builder
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfbuildersetrndvarsratio(decisionforestbuilder s, double f)
+    {
+    
+        dforest.dfbuildersetrndvarsratio(s.innerobj, f, null);
+    }
+    
+    public static void dfbuildersetrndvarsratio(decisionforestbuilder s, double f, alglib.xparams _params)
+    {
+    
+        dforest.dfbuildersetrndvarsratio(s.innerobj, f, _params);
+    }
+    
+    /*************************************************************************
+    This function tells random forest builder to automatically  choose  number
+    of  variables  used  by  random  forest  construction  algorithm.  Roughly
+    sqrt(NVars) variables will be used.
+
+    INPUT PARAMETERS:
+        S           -   decision forest builder object
+
+    OUTPUT PARAMETERS:
+        S           -   decision forest builder
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfbuildersetrndvarsauto(decisionforestbuilder s)
+    {
+    
+        dforest.dfbuildersetrndvarsauto(s.innerobj, null);
+    }
+    
+    public static void dfbuildersetrndvarsauto(decisionforestbuilder s, alglib.xparams _params)
+    {
+    
+        dforest.dfbuildersetrndvarsauto(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function sets size of dataset subsample generated the  random  forest
+    construction algorithm. Size is specified as a fraction of  total  dataset
+    size.
+
+    The default option is to use 50% of the dataset for training, 50% for  the
+    OOB estimates. You can decrease fraction F down to 10%, 1% or  even  below
+    in order to reduce overfitting.
+
+    INPUT PARAMETERS:
+        S           -   decision forest builder object
+        F           -   fraction of the dataset to use, in (0,1] range. Values
+                        outside of this range will  be  silently  clipped.  At
+                        least one element is always selected for the  training
+                        set.
+
+    OUTPUT PARAMETERS:
+        S           -   decision forest builder
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfbuildersetsubsampleratio(decisionforestbuilder s, double f)
+    {
+    
+        dforest.dfbuildersetsubsampleratio(s.innerobj, f, null);
+    }
+    
+    public static void dfbuildersetsubsampleratio(decisionforestbuilder s, double f, alglib.xparams _params)
+    {
+    
+        dforest.dfbuildersetsubsampleratio(s.innerobj, f, _params);
+    }
+    
+    /*************************************************************************
+    This function sets seed used by internal RNG for  random  subsampling  and
+    random selection of variable subsets.
+
+    By default, random seed is used, i.e. every time you build random  forest,
+    we seed generator with new value  obtained  from  system-wide  RNG.  Thus,
+    decision forest builder returns non-deterministic results. You can  change
+    such behavior by specyfing fixed positive seed value.
+
+    INPUT PARAMETERS:
+        S           -   decision forest builder object
+        SeedVal     -   seed value:
+                        * positive values are used for seeding RNG with fixed
+                          seed, i.e. subsequent runs on same data will return
+                          same random forests
+                        * non-positive seed means that random seed is used
+                          for every run of builder, i.e. subsequent  runs  on
+                          same datasets will return slightly different random
+                          forests
+
+    OUTPUT PARAMETERS:
+        S           -   decision forest builder, see
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfbuildersetseed(decisionforestbuilder s, int seedval)
+    {
+    
+        dforest.dfbuildersetseed(s.innerobj, seedval, null);
+    }
+    
+    public static void dfbuildersetseed(decisionforestbuilder s, int seedval, alglib.xparams _params)
+    {
+    
+        dforest.dfbuildersetseed(s.innerobj, seedval, _params);
+    }
+    
+    /*************************************************************************
+    This function sets random decision forest construction algorithm.
+
+    As for now, only one random forest construction algorithm is  supported  -
+    a dense "baseline" RDF algorithm.
+
+    INPUT PARAMETERS:
+        S           -   decision forest builder object
+        AlgoType    -   algorithm type:
+                        * 0 = baseline dense RDF
+
+    OUTPUT PARAMETERS:
+        S           -   decision forest builder, see
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfbuildersetrdfalgo(decisionforestbuilder s, int algotype)
+    {
+    
+        dforest.dfbuildersetrdfalgo(s.innerobj, algotype, null);
+    }
+    
+    public static void dfbuildersetrdfalgo(decisionforestbuilder s, int algotype, alglib.xparams _params)
+    {
+    
+        dforest.dfbuildersetrdfalgo(s.innerobj, algotype, _params);
+    }
+    
+    /*************************************************************************
+    This  function  sets  split  selection  algorithm  used  by random forests
+    classifier. You may choose several algorithms, with  different  speed  and
+    quality of the results.
+
+    INPUT PARAMETERS:
+        S           -   decision forest builder object
+        SplitStrength-  split type:
+                        * 0 = split at the random position, fastest one
+                        * 1 = split at the middle of the range
+                        * 2 = strong split at the best point of the range (default)
+
+    OUTPUT PARAMETERS:
+        S           -   decision forest builder, see
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfbuildersetrdfsplitstrength(decisionforestbuilder s, int splitstrength)
+    {
+    
+        dforest.dfbuildersetrdfsplitstrength(s.innerobj, splitstrength, null);
+    }
+    
+    public static void dfbuildersetrdfsplitstrength(decisionforestbuilder s, int splitstrength, alglib.xparams _params)
+    {
+    
+        dforest.dfbuildersetrdfsplitstrength(s.innerobj, splitstrength, _params);
+    }
+    
+    /*************************************************************************
+    This function is an alias for dfbuilderpeekprogress(), left in ALGLIB  for
+    backward compatibility reasons.
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static double dfbuildergetprogress(decisionforestbuilder s)
+    {
+    
+        return dforest.dfbuildergetprogress(s.innerobj, null);
+    }
+    
+    public static double dfbuildergetprogress(decisionforestbuilder s, alglib.xparams _params)
+    {
+    
+        return dforest.dfbuildergetprogress(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function is used to peek into random forest construction process from
+    other thread and get current progress indicator. It returns value in [0,1].
+
+    You can "peek" into decision forest builder from another thread.
+
+    INPUT PARAMETERS:
+        S           -   decision forest builder object used  to  build  random
+                        forest in some other thread
+
+    RESULT:
+        progress value, in [0,1]
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static double dfbuilderpeekprogress(decisionforestbuilder s)
+    {
+    
+        return dforest.dfbuilderpeekprogress(s.innerobj, null);
+    }
+    
+    public static double dfbuilderpeekprogress(decisionforestbuilder s, alglib.xparams _params)
+    {
+    
+        return dforest.dfbuilderpeekprogress(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine builds random forest according to current settings,  using
+    dataset internally stored in the builder object. Dense algorithm is used.
+
+    NOTE: this   function   uses   dense  algorithm  for  forest  construction
+          independently from the dataset format (dense or sparse).
+
+    Default settings are used by the algorithm; you can tweak  them  with  the
+    help of the following functions:
+    * dfbuildersetrfactor() - to control a fraction of the  dataset  used  for
+      subsampling
+    * dfbuildersetrandomvars() - to control number of variables randomly chosen
+      for decision rule creation
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        S           -   decision forest builder object
+        NTrees      -   NTrees>=1, number of trees to train
+
+    OUTPUT PARAMETERS:
+        DF          -   decision forest
+        Rep         -   report
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void dfbuilderbuildrandomforest(decisionforestbuilder s, int ntrees, out decisionforest df, out dfreport rep)
+    {
+        df = new decisionforest();
+        rep = new dfreport();
+        dforest.dfbuilderbuildrandomforest(s.innerobj, ntrees, df.innerobj, rep.innerobj, null);
+    }
+    
+    public static void dfbuilderbuildrandomforest(decisionforestbuilder s, int ntrees, out decisionforest df, out dfreport rep, alglib.xparams _params)
+    {
+        df = new decisionforest();
+        rep = new dfreport();
+        dforest.dfbuilderbuildrandomforest(s.innerobj, ntrees, df.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Inference using decision forest
+
+    IMPORTANT: this  function  is  thread-unsafe  and  may   modify   internal
+               structures of the model! You can not use same model  object for
+               parallel evaluation from several threads.
+
+               Use dftsprocess()  with  independent  thread-local  buffers  if
+               you need thread-safe evaluation.
+
+    INPUT PARAMETERS:
+        DF      -   decision forest model
+        X       -   input vector,  array[NVars]
+        Y       -   possibly preallocated buffer, reallocated if too small
+
+    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, null);
+    }
+    
+    public static void dfprocess(decisionforest df, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        dforest.dfprocess(df.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    '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.
+
+    IMPORTANT: this  function  is  thread-unsafe  and  may   modify   internal
+               structures of the model! You can not use same model  object for
+               parallel evaluation from several threads.
+
+               Use dftsprocess()  with  independent  thread-local  buffers  if
+               you need thread-safe evaluation.
+
+      -- 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, null);
+    }
+    
+    public static void dfprocessi(decisionforest df, double[] x, out double[] y, alglib.xparams _params)
+    {
+        y = new double[0];
+        dforest.dfprocessi(df.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function returns first component of the  inferred  vector  (i.e.  one
+    with index #0).
+
+    It is a convenience wrapper for dfprocess() intended for either:
+    * 1-dimensional regression problems
+    * 2-class classification problems
+
+    In the former case this function returns inference result as scalar, which
+    is definitely more convenient that wrapping it as vector.  In  the  latter
+    case it returns probability of object belonging to class #0.
+
+    If you call it for anything different from two cases above, it  will  work
+    as defined, i.e. return y[0], although it is of less use in such cases.
+
+    IMPORTANT: this function is thread-unsafe and modifies internal structures
+               of the model! You can not use same model  object  for  parallel
+               evaluation from several threads.
+
+               Use dftsprocess() with  independent  thread-local  buffers,  if
+               you need thread-safe evaluation.
+
+    INPUT PARAMETERS:
+        Model   -   DF model
+        X       -   input vector,  array[0..NVars-1].
+
+    RESULT:
+        Y[0]
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static double dfprocess0(decisionforest model, double[] x)
+    {
+    
+        return dforest.dfprocess0(model.innerobj, x, null);
+    }
+    
+    public static double dfprocess0(decisionforest model, double[] x, alglib.xparams _params)
+    {
+    
+        return dforest.dfprocess0(model.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    This function returns most probable class number for an  input  X.  It  is
+    same as calling  dfprocess(model,x,y), then determining i=argmax(y[i]) and
+    returning i.
+
+    A class number in [0,NOut) range in returned for classification  problems,
+    -1 is returned when this function is called for regression problems.
+
+    IMPORTANT: this function is thread-unsafe and modifies internal structures
+               of the model! You can not use same model  object  for  parallel
+               evaluation from several threads.
+
+               Use dftsprocess()  with independent  thread-local  buffers,  if
+               you need thread-safe evaluation.
+
+    INPUT PARAMETERS:
+        Model   -   decision forest model
+        X       -   input vector,  array[0..NVars-1].
+
+    RESULT:
+        class number, -1 for regression tasks
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static int dfclassify(decisionforest model, double[] x)
+    {
+    
+        return dforest.dfclassify(model.innerobj, x, null);
+    }
+    
+    public static int dfclassify(decisionforest model, double[] x, alglib.xparams _params)
+    {
+    
+        return dforest.dfclassify(model.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    Inference using decision forest
+
+    Thread-safe procesing using external buffer for temporaries.
+
+    This function is thread-safe (i.e .  you  can  use  same  DF   model  from
+    multiple threads) as long as you use different buffer objects for different
+    threads.
+
+    INPUT PARAMETERS:
+        DF      -   decision forest model
+        Buf     -   buffer object, must be  allocated  specifically  for  this
+                    model with dfcreatebuffer().
+        X       -   input vector,  array[NVars]
+        Y       -   possibly preallocated buffer, reallocated if too small
+
+    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 dftsprocess(decisionforest df, decisionforestbuffer buf, double[] x, ref double[] y)
+    {
+    
+        dforest.dftsprocess(df.innerobj, buf.innerobj, x, ref y, null);
+    }
+    
+    public static void dftsprocess(decisionforest df, decisionforestbuffer buf, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        dforest.dftsprocess(df.innerobj, buf.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return dforest.dfrelclserror(df.innerobj, xy, npoints, null);
+    }
+    
+    public static double dfrelclserror(decisionforest df, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return dforest.dfrelclserror(df.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return dforest.dfavgce(df.innerobj, xy, npoints, null);
+    }
+    
+    public static double dfavgce(decisionforest df, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return dforest.dfavgce(df.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return dforest.dfrmserror(df.innerobj, xy, npoints, null);
+    }
+    
+    public static double dfrmserror(decisionforest df, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return dforest.dfrmserror(df.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return dforest.dfavgerror(df.innerobj, xy, npoints, null);
+    }
+    
+    public static double dfavgerror(decisionforest df, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return dforest.dfavgerror(df.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return dforest.dfavgrelerror(df.innerobj, xy, npoints, null);
+    }
+    
+    public static double dfavgrelerror(decisionforest df, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return dforest.dfavgrelerror(df.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine builds random decision forest.
+
+    --------- DEPRECATED VERSION! USE DECISION FOREST BUILDER OBJECT ---------
+
+      -- 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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        info = 0;
+        df = new decisionforest();
+        rep = new dfreport();
+        dforest.dfbuildrandomdecisionforest(xy, npoints, nvars, nclasses, ntrees, r, ref info, df.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine builds random decision forest.
+
+    --------- DEPRECATED VERSION! USE DECISION FOREST BUILDER OBJECT ---------
+
+      -- 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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        info = 0;
+        df = new decisionforest();
+        rep = new dfreport();
+        dforest.dfbuildrandomdecisionforestx1(xy, npoints, nvars, nclasses, ntrees, nrndvars, r, ref info, df.innerobj, rep.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Buffer object which is used to perform  various  requests  (usually  model
+    inference) in the multithreaded mode (multiple threads working  with  same
+    KNN object).
+
+    This object should be created with KNNCreateBuffer().
+    *************************************************************************/
+    public class knnbuffer : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public knnbuffer()
+        {
+            _innerobj = new knn.knnbuffer();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new knnbuffer((knn.knnbuffer)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private knn.knnbuffer _innerobj;
+        public knn.knnbuffer innerobj { get { return _innerobj; } }
+        public knnbuffer(knn.knnbuffer obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    A KNN builder object; this object encapsulates  dataset  and  all  related
+    settings, it is used to create an actual instance of KNN model.
+    *************************************************************************/
+    public class knnbuilder : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public knnbuilder()
+        {
+            _innerobj = new knn.knnbuilder();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new knnbuilder((knn.knnbuilder)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private knn.knnbuilder _innerobj;
+        public knn.knnbuilder innerobj { get { return _innerobj; } }
+        public knnbuilder(knn.knnbuilder obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    KNN model, can be used for classification or regression
+    *************************************************************************/
+    public class knnmodel : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public knnmodel()
+        {
+            _innerobj = new knn.knnmodel();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new knnmodel((knn.knnmodel)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private knn.knnmodel _innerobj;
+        public knn.knnmodel innerobj { get { return _innerobj; } }
+        public knnmodel(knn.knnmodel obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    KNN training report.
+
+    Following fields store training set errors:
+    * relclserror       -   fraction of misclassified cases, [0,1]
+    * avgce             -   average cross-entropy in bits per symbol
+    * rmserror          -   root-mean-square error
+    * avgerror          -   average error
+    * avgrelerror       -   average relative error
+
+    For classification problems:
+    * RMS, AVG and AVGREL errors are calculated for posterior probabilities
+
+    For regression problems:
+    * RELCLS and AVGCE errors are zero
+    *************************************************************************/
+    public class knnreport : alglibobject
+    {
+        //
+        // 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 knnreport()
+        {
+            _innerobj = new knn.knnreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new knnreport((knn.knnreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private knn.knnreport _innerobj;
+        public knn.knnreport innerobj { get { return _innerobj; } }
+        public knnreport(knn.knnreport 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 knnserialize(knnmodel obj, out string s_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        knn.knnalloc(s, obj.innerobj, null);
+        s.sstart_str();
+        knn.knnserialize(s, obj.innerobj, null);
+        s.stop();
+        s_out = s.get_string();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from string.
+    *************************************************************************/
+    public static void knnunserialize(string s_in, out knnmodel obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new knnmodel();
+        s.ustart_str(s_in);
+        knn.knnunserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to stream.
+    
+    Data stream generated by this function is same as  string  representation
+    generated  by  string  version  of  serializer - alphanumeric characters,
+    dots, underscores, minus signs, which are grouped into words separated by
+    spaces and CR+LF.
+    
+    We recommend you to read comments on string version of serializer to find
+    out more about serialization of AlGLIB objects.
+    *************************************************************************/
+    public static void knnserialize(knnmodel obj, System.IO.Stream stream_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        knn.knnalloc(s, obj.innerobj, null);
+        s.sstart_stream(stream_out);
+        knn.knnserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from stream.
+    *************************************************************************/
+    public static void knnunserialize(System.IO.Stream stream_in, out knnmodel obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new knnmodel();
+        s.ustart_stream(stream_in);
+        knn.knnunserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+    
+    /*************************************************************************
+    This function creates buffer  structure  which  can  be  used  to  perform
+    parallel KNN requests.
+
+    KNN subpackage provides two sets of computing functions - ones  which  use
+    internal buffer of KNN model (these  functions are single-threaded because
+    they use same buffer, which can not  shared  between  threads),  and  ones
+    which use external buffer.
+
+    This function is used to initialize external buffer.
+
+    INPUT PARAMETERS
+        Model       -   KNN model which is associated with newly created buffer
+
+    OUTPUT PARAMETERS
+        Buf         -   external buffer.
+
+
+    IMPORTANT: buffer object should be used only with model which was used  to
+               initialize buffer. Any attempt to  use  buffer  with  different
+               object is dangerous - you  may   get  integrity  check  failure
+               (exception) because sizes of internal  arrays  do  not  fit  to
+               dimensions of the model structure.
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static void knncreatebuffer(knnmodel model, out knnbuffer buf)
+    {
+        buf = new knnbuffer();
+        knn.knncreatebuffer(model.innerobj, buf.innerobj, null);
+    }
+    
+    public static void knncreatebuffer(knnmodel model, out knnbuffer buf, alglib.xparams _params)
+    {
+        buf = new knnbuffer();
+        knn.knncreatebuffer(model.innerobj, buf.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine creates KNNBuilder object which is used to train KNN models.
+
+    By default, new builder stores empty dataset and some  reasonable  default
+    settings. At the very least, you should specify dataset prior to  building
+    KNN model. You can also tweak settings of the model construction algorithm
+    (recommended, although default settings should work well).
+
+    Following actions are mandatory:
+    * calling knnbuildersetdataset() to specify dataset
+    * calling knnbuilderbuildknnmodel() to build KNN model using current
+      dataset and default settings
+
+    Additionally, you may call:
+    * knnbuildersetnorm() to change norm being used
+
+    INPUT PARAMETERS:
+        none
+
+    OUTPUT PARAMETERS:
+        S           -   KNN builder
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static void knnbuildercreate(out knnbuilder s)
+    {
+        s = new knnbuilder();
+        knn.knnbuildercreate(s.innerobj, null);
+    }
+    
+    public static void knnbuildercreate(out knnbuilder s, alglib.xparams _params)
+    {
+        s = new knnbuilder();
+        knn.knnbuildercreate(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Specifies regression problem (one or more continuous  output variables are
+    predicted). There also exists "classification" version of this function.
+
+    This subroutine adds dense dataset to the internal storage of the  builder
+    object. Specifying your dataset in the dense format means that  the  dense
+    version of the KNN construction algorithm will be invoked.
+
+    INPUT PARAMETERS:
+        S           -   KNN builder object
+        XY          -   array[NPoints,NVars+NOut] (note: actual  size  can  be
+                        larger, only leading part is used anyway), dataset:
+                        * first NVars elements of each row store values of the
+                          independent variables
+                        * next NOut elements store  values  of  the  dependent
+                          variables
+        NPoints     -   number of rows in the dataset, NPoints>=1
+        NVars       -   number of independent variables, NVars>=1
+        NOut        -   number of dependent variables, NOut>=1
+
+    OUTPUT PARAMETERS:
+        S           -   KNN builder
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static void knnbuildersetdatasetreg(knnbuilder s, double[,] xy, int npoints, int nvars, int nout)
+    {
+    
+        knn.knnbuildersetdatasetreg(s.innerobj, xy, npoints, nvars, nout, null);
+    }
+    
+    public static void knnbuildersetdatasetreg(knnbuilder s, double[,] xy, int npoints, int nvars, int nout, alglib.xparams _params)
+    {
+    
+        knn.knnbuildersetdatasetreg(s.innerobj, xy, npoints, nvars, nout, _params);
+    }
+    
+    /*************************************************************************
+    Specifies classification problem (two  or  more  classes  are  predicted).
+    There also exists "regression" version of this function.
+
+    This subroutine adds dense dataset to the internal storage of the  builder
+    object. Specifying your dataset in the dense format means that  the  dense
+    version of the KNN construction algorithm will be invoked.
+
+    INPUT PARAMETERS:
+        S           -   KNN builder object
+        XY          -   array[NPoints,NVars+1] (note:   actual   size  can  be
+                        larger, only leading part is used anyway), dataset:
+                        * first NVars elements of each row store values of the
+                          independent variables
+                        * next element stores class index, in [0,NClasses)
+        NPoints     -   number of rows in the dataset, NPoints>=1
+        NVars       -   number of independent variables, NVars>=1
+        NClasses    -   number of classes, NClasses>=2
+
+    OUTPUT PARAMETERS:
+        S           -   KNN builder
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static void knnbuildersetdatasetcls(knnbuilder s, double[,] xy, int npoints, int nvars, int nclasses)
+    {
+    
+        knn.knnbuildersetdatasetcls(s.innerobj, xy, npoints, nvars, nclasses, null);
+    }
+    
+    public static void knnbuildersetdatasetcls(knnbuilder s, double[,] xy, int npoints, int nvars, int nclasses, alglib.xparams _params)
+    {
+    
+        knn.knnbuildersetdatasetcls(s.innerobj, xy, npoints, nvars, nclasses, _params);
+    }
+    
+    /*************************************************************************
+    This function sets norm type used for neighbor search.
+
+    INPUT PARAMETERS:
+        S           -   decision forest builder object
+        NormType    -   norm type:
+                        * 0      inf-norm
+                        * 1      1-norm
+                        * 2      Euclidean norm (default)
+
+    OUTPUT PARAMETERS:
+        S           -   decision forest builder
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static void knnbuildersetnorm(knnbuilder s, int nrmtype)
+    {
+    
+        knn.knnbuildersetnorm(s.innerobj, nrmtype, null);
+    }
+    
+    public static void knnbuildersetnorm(knnbuilder s, int nrmtype, alglib.xparams _params)
+    {
+    
+        knn.knnbuildersetnorm(s.innerobj, nrmtype, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine builds KNN model  according  to  current  settings,  using
+    dataset internally stored in the builder object.
+
+    The model being built performs inference using Eps-approximate  K  nearest
+    neighbors search algorithm, with:
+    * K=1,  Eps=0 corresponding to the "nearest neighbor algorithm"
+    * K>1,  Eps=0 corresponding to the "K nearest neighbors algorithm"
+    * K>=1, Eps>0 corresponding to "approximate nearest neighbors algorithm"
+
+    An approximate KNN is a good option for high-dimensional  datasets  (exact
+    KNN works slowly when dimensions count grows).
+
+    An ALGLIB implementation of kd-trees is used to perform k-nn searches.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        S       -   KNN builder object
+        K       -   number of neighbors to search for, K>=1
+        Eps     -   approximation factor:
+                    * Eps=0 means that exact kNN search is performed
+                    * Eps>0 means that (1+Eps)-approximate search is performed
+
+    OUTPUT PARAMETERS:
+        Model       -   KNN model
+        Rep         -   report
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static void knnbuilderbuildknnmodel(knnbuilder s, int k, double eps, out knnmodel model, out knnreport rep)
+    {
+        model = new knnmodel();
+        rep = new knnreport();
+        knn.knnbuilderbuildknnmodel(s.innerobj, k, eps, model.innerobj, rep.innerobj, null);
+    }
+    
+    public static void knnbuilderbuildknnmodel(knnbuilder s, int k, double eps, out knnmodel model, out knnreport rep, alglib.xparams _params)
+    {
+        model = new knnmodel();
+        rep = new knnreport();
+        knn.knnbuilderbuildknnmodel(s.innerobj, k, eps, model.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Changing search settings of KNN model.
+
+    K and EPS parameters of KNN  (AKNN)  search  are  specified  during  model
+    construction. However, plain KNN algorithm with Euclidean distance  allows
+    you to change them at any moment.
+
+    NOTE: future versions of KNN model may support advanced versions  of  KNN,
+          such as NCA or LMNN. It is possible that such algorithms won't allow
+          you to change search settings on the fly. If you call this  function
+          for an algorithm which does not support on-the-fly changes, it  will
+          throw an exception.
+
+    INPUT PARAMETERS:
+        Model   -   KNN model
+        K       -   K>=1, neighbors count
+        EPS     -   accuracy of the EPS-approximate NN search. Set to 0.0,  if
+                    you want to perform "classic" KNN search.  Specify  larger
+                    values  if  you  need  to  speed-up  high-dimensional  KNN
+                    queries.
+
+    OUTPUT PARAMETERS:
+        nothing on success, exception on failure
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static void knnrewritekeps(knnmodel model, int k, double eps)
+    {
+    
+        knn.knnrewritekeps(model.innerobj, k, eps, null);
+    }
+    
+    public static void knnrewritekeps(knnmodel model, int k, double eps, alglib.xparams _params)
+    {
+    
+        knn.knnrewritekeps(model.innerobj, k, eps, _params);
+    }
+    
+    /*************************************************************************
+    Inference using KNN model.
+
+    See also knnprocess0(), knnprocessi() and knnclassify() for options with a
+    bit more convenient interface.
+
+    IMPORTANT: this function is thread-unsafe and modifies internal structures
+               of the model! You can not use same model  object  for  parallel
+               evaluation from several threads.
+
+               Use knntsprocess() with independent  thread-local  buffers,  if
+               you need thread-safe evaluation.
+
+    INPUT PARAMETERS:
+        Model   -   KNN model
+        X       -   input vector,  array[0..NVars-1].
+        Y       -   possible preallocated buffer. Reused if long enough.
+
+    OUTPUT PARAMETERS:
+        Y       -   result. Regression estimate when solving regression  task,
+                    vector of posterior probabilities for classification task.
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static void knnprocess(knnmodel model, double[] x, ref double[] y)
+    {
+    
+        knn.knnprocess(model.innerobj, x, ref y, null);
+    }
+    
+    public static void knnprocess(knnmodel model, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        knn.knnprocess(model.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function returns first component of the  inferred  vector  (i.e.  one
+    with index #0).
+
+    It is a convenience wrapper for knnprocess() intended for either:
+    * 1-dimensional regression problems
+    * 2-class classification problems
+
+    In the former case this function returns inference result as scalar, which
+    is definitely more convenient that wrapping it as vector.  In  the  latter
+    case it returns probability of object belonging to class #0.
+
+    If you call it for anything different from two cases above, it  will  work
+    as defined, i.e. return y[0], although it is of less use in such cases.
+
+    IMPORTANT: this function is thread-unsafe and modifies internal structures
+               of the model! You can not use same model  object  for  parallel
+               evaluation from several threads.
+
+               Use knntsprocess() with independent  thread-local  buffers,  if
+               you need thread-safe evaluation.
+
+    INPUT PARAMETERS:
+        Model   -   KNN model
+        X       -   input vector,  array[0..NVars-1].
+
+    RESULT:
+        Y[0]
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static double knnprocess0(knnmodel model, double[] x)
+    {
+    
+        return knn.knnprocess0(model.innerobj, x, null);
+    }
+    
+    public static double knnprocess0(knnmodel model, double[] x, alglib.xparams _params)
+    {
+    
+        return knn.knnprocess0(model.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    This function returns most probable class number for an  input  X.  It  is
+    same as calling knnprocess(model,x,y), then determining i=argmax(y[i]) and
+    returning i.
+
+    A class number in [0,NOut) range in returned for classification  problems,
+    -1 is returned when this function is called for regression problems.
+
+    IMPORTANT: this function is thread-unsafe and modifies internal structures
+               of the model! You can not use same model  object  for  parallel
+               evaluation from several threads.
+
+               Use knntsprocess() with independent  thread-local  buffers,  if
+               you need thread-safe evaluation.
+
+    INPUT PARAMETERS:
+        Model   -   KNN model
+        X       -   input vector,  array[0..NVars-1].
+
+    RESULT:
+        class number, -1 for regression tasks
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static int knnclassify(knnmodel model, double[] x)
+    {
+    
+        return knn.knnclassify(model.innerobj, x, null);
+    }
+    
+    public static int knnclassify(knnmodel model, double[] x, alglib.xparams _params)
+    {
+    
+        return knn.knnclassify(model.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    'interactive' variant of knnprocess()  for  languages  like  Python  which
+    support constructs like "y = knnprocessi(model,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.
+
+    IMPORTANT: this  function  is  thread-unsafe  and  may   modify   internal
+               structures of the model! You can not use same model  object for
+               parallel evaluation from several threads.
+
+               Use knntsprocess()  with  independent  thread-local  buffers if
+               you need thread-safe evaluation.
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static void knnprocessi(knnmodel model, double[] x, out double[] y)
+    {
+        y = new double[0];
+        knn.knnprocessi(model.innerobj, x, ref y, null);
+    }
+    
+    public static void knnprocessi(knnmodel model, double[] x, out double[] y, alglib.xparams _params)
+    {
+        y = new double[0];
+        knn.knnprocessi(model.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    Thread-safe procesing using external buffer for temporaries.
+
+    This function is thread-safe (i.e .  you  can  use  same  KNN  model  from
+    multiple threads) as long as you use different buffer objects for different
+    threads.
+
+    INPUT PARAMETERS:
+        Model   -   KNN model
+        Buf     -   buffer object, must be  allocated  specifically  for  this
+                    model with knncreatebuffer().
+        X       -   input vector,  array[NVars]
+
+    OUTPUT PARAMETERS:
+        Y       -   result, array[NOut].   Regression  estimate  when  solving
+                    regression task,  vector  of  posterior  probabilities for
+                    a classification task.
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static void knntsprocess(knnmodel model, knnbuffer buf, double[] x, ref double[] y)
+    {
+    
+        knn.knntsprocess(model.innerobj, buf.innerobj, x, ref y, null);
+    }
+    
+    public static void knntsprocess(knnmodel model, knnbuffer buf, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        knn.knntsprocess(model.innerobj, buf.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    Relative classification error on the test set
+
+    INPUT PARAMETERS:
+        Model   -   KNN model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        percent of incorrectly classified cases.
+        Zero if model solves regression task.
+
+    NOTE: if  you  need several different kinds of error metrics, it is better
+          to use knnallerrors() which computes all error metric  with just one
+          pass over dataset.
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static double knnrelclserror(knnmodel model, double[,] xy, int npoints)
+    {
+    
+        return knn.knnrelclserror(model.innerobj, xy, npoints, null);
+    }
+    
+    public static double knnrelclserror(knnmodel model, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return knn.knnrelclserror(model.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Average cross-entropy (in bits per element) on the test set
+
+    INPUT PARAMETERS:
+        Model   -   KNN model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        CrossEntropy/NPoints.
+        Zero if model solves regression task.
+
+    NOTE: the cross-entropy metric is too unstable when used to  evaluate  KNN
+          models (such models can report exactly  zero probabilities),  so  we
+          do not recommend using it.
+
+    NOTE: if  you  need several different kinds of error metrics, it is better
+          to use knnallerrors() which computes all error metric  with just one
+          pass over dataset.
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static double knnavgce(knnmodel model, double[,] xy, int npoints)
+    {
+    
+        return knn.knnavgce(model.innerobj, xy, npoints, null);
+    }
+    
+    public static double knnavgce(knnmodel model, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return knn.knnavgce(model.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    RMS error on the test set.
+
+    Its meaning for regression task is obvious. As for classification problems,
+    RMS error means error when estimating posterior probabilities.
+
+    INPUT PARAMETERS:
+        Model   -   KNN model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        root mean square error.
+
+    NOTE: if  you  need several different kinds of error metrics, it is better
+          to use knnallerrors() which computes all error metric  with just one
+          pass over dataset.
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static double knnrmserror(knnmodel model, double[,] xy, int npoints)
+    {
+    
+        return knn.knnrmserror(model.innerobj, xy, npoints, null);
+    }
+    
+    public static double knnrmserror(knnmodel model, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return knn.knnrmserror(model.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Average error on the test set
+
+    Its meaning for regression task is obvious. As for classification problems,
+    average error means error when estimating posterior probabilities.
+
+    INPUT PARAMETERS:
+        Model   -   KNN model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        average error
+
+    NOTE: if  you  need several different kinds of error metrics, it is better
+          to use knnallerrors() which computes all error metric  with just one
+          pass over dataset.
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static double knnavgerror(knnmodel model, double[,] xy, int npoints)
+    {
+    
+        return knn.knnavgerror(model.innerobj, xy, npoints, null);
+    }
+    
+    public static double knnavgerror(knnmodel model, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return knn.knnavgerror(model.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Average relative error on the test set
+
+    Its meaning for regression task is obvious. As for classification problems,
+    average relative error means error when estimating posterior probabilities.
+
+    INPUT PARAMETERS:
+        Model   -   KNN model
+        XY      -   test set
+        NPoints -   test set size
+
+    RESULT:
+        average relative error
+
+    NOTE: if  you  need several different kinds of error metrics, it is better
+          to use knnallerrors() which computes all error metric  with just one
+          pass over dataset.
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static double knnavgrelerror(knnmodel model, double[,] xy, int npoints)
+    {
+    
+        return knn.knnavgrelerror(model.innerobj, xy, npoints, null);
+    }
+    
+    public static double knnavgrelerror(knnmodel model, double[,] xy, int npoints, alglib.xparams _params)
+    {
+    
+        return knn.knnavgrelerror(model.innerobj, xy, npoints, _params);
+    }
+    
+    /*************************************************************************
+    Calculates all kinds of errors for the model in one call.
+
+    INPUT PARAMETERS:
+        Model   -   KNN model
+        XY      -   test set:
+                    * one row per point
+                    * first NVars columns store independent variables
+                    * depending on problem type:
+                      * next column stores class number in [0,NClasses) -  for
+                        classification problems
+                      * next NOut columns  store  dependent  variables  -  for
+                        regression problems
+        NPoints -   test set size, NPoints>=0
+
+    OUTPUT PARAMETERS:
+        Rep     -   following fields are loaded with errors for both regression
+                    and classification models:
+                    * rep.rmserror - RMS error for the output
+                    * rep.avgerror - average error
+                    * rep.avgrelerror - average relative error
+                    following fields are set only  for classification  models,
+                    zero for regression ones:
+                    * relclserror   - relative classification error, in [0,1]
+                    * avgce - average cross-entropy in bits per dataset entry
+
+    NOTE: the cross-entropy metric is too unstable when used to  evaluate  KNN
+          models (such models can report exactly  zero probabilities),  so  we
+          do not recommend using it.
+
+      -- ALGLIB --
+         Copyright 15.02.2019 by Bochkanov Sergey
+    *************************************************************************/
+    public static void knnallerrors(knnmodel model, double[,] xy, int npoints, out knnreport rep)
+    {
+        rep = new knnreport();
+        knn.knnallerrors(model.innerobj, xy, npoints, rep.innerobj, null);
+    }
+    
+    public static void knnallerrors(knnmodel model, double[,] xy, int npoints, out knnreport rep, alglib.xparams _params)
+    {
+        rep = new knnreport();
+        knn.knnallerrors(model.innerobj, xy, npoints, rep.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+    
+    /*************************************************************************
+    k-means++ clusterization.
+    Backward compatibility function, we recommend to use CLUSTERING subpackage
+    as better replacement.
+
+      -- 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];
+        datacomp.kmeansgenerate(xy, npoints, nvars, k, restarts, ref info, ref c, ref xyc, null);
+    }
+    
+    public static void kmeansgenerate(double[,] xy, int npoints, int nvars, int k, int restarts, out int info, out double[,] c, out int[] xyc, alglib.xparams _params)
+    {
+        info = 0;
+        c = new double[0,0];
+        xyc = new int[0];
+        datacomp.kmeansgenerate(xy, npoints, nvars, k, restarts, ref info, ref c, ref xyc, _params);
+    }
+
+}
+public partial class alglib
+{
+    public class pca
+    {
+        /*************************************************************************
+        Principal components analysis
+
+        This function builds orthogonal basis  where  first  axis  corresponds  to
+        direction with maximum variance, second axis  maximizes  variance  in  the
+        subspace orthogonal to first axis and so on.
+
+        This function builds FULL basis, i.e. returns N vectors  corresponding  to
+        ALL directions, no matter how informative. If you need  just a  few  (say,
+        10 or 50) of the most important directions, you may find it faster to  use
+        one of the reduced versions:
+        * pcatruncatedsubspace() - for subspace iteration based method
+
+        It should be noted that, unlike LDA, PCA does not use class labels.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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
+
+        OUTPUT PARAMETERS:
+            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,
+            alglib.xparams _params)
+        {
+            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];
+                v = new double[nvars, nvars];
+                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];
+            t = new double[npoints];
+            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, _params);
+                m[j] = mean;
+            }
+            
+            //
+            // Center, apply SVD, prepare output
+            //
+            a = new double[Math.Max(npoints, nvars), nvars];
+            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, _params) )
+            {
+                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, nvars];
+            blas.copyandtranspose(vt, 0, nvars-1, 0, nvars-1, ref v, 0, nvars-1, 0, nvars-1, _params);
+        }
+
+
+        /*************************************************************************
+        Principal components analysis
+
+        This function performs truncated PCA, i.e. returns just a few most important
+        directions.
+
+        Internally it uses iterative eigensolver which is very efficient when only
+        a minor fraction of full basis is required. Thus, if you need full  basis,
+        it is better to use pcabuildbasis() function.
+
+        It should be noted that, unlike LDA, PCA does not use class labels.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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
+            NNeeded     -   number of requested components, in [1,NVars] range;
+                            this function is efficient only for NNeeded<<NVars.
+            Eps         -   desired  precision  of  vectors  returned;  underlying
+                            solver will stop iterations as soon as absolute  error
+                            in corresponding singular values  reduces  to  roughly
+                            eps*MAX(lambda[]), with lambda[] being array of  eigen
+                            values.
+                            Zero value means that  algorithm  performs  number  of
+                            iterations  specified  by  maxits  parameter,  without
+                            paying attention to precision.
+            MaxIts      -   number of iterations performed by  subspace  iteration
+                            method. Zero value means that no  limit  on  iteration
+                            count is placed (eps-based stopping condition is used).
+                            
+
+        OUTPUT PARAMETERS:
+            S2          -   array[NNeeded]. Variance values corresponding
+                            to basis vectors.
+            V           -   array[NVars,NNeeded]
+                            matrix, whose columns store basis vectors.
+                            
+        NOTE: passing eps=0 and maxits=0 results in small eps  being  selected  as
+        stopping condition. Exact value of automatically selected eps is  version-
+        -dependent.
+
+          -- ALGLIB --
+             Copyright 10.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pcatruncatedsubspace(double[,] x,
+            int npoints,
+            int nvars,
+            int nneeded,
+            double eps,
+            int maxits,
+            ref double[] s2,
+            ref double[,] v,
+            alglib.xparams _params)
+        {
+            double[,] a = new double[0,0];
+            double[,] b = new double[0,0];
+            double[] means = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double vv = 0;
+            evd.eigsubspacestate solver = new evd.eigsubspacestate();
+            evd.eigsubspacereport rep = new evd.eigsubspacereport();
+            int i_ = 0;
+
+            s2 = new double[0];
+            v = new double[0,0];
+
+            alglib.ap.assert(npoints>=0, "PCATruncatedSubspace: npoints<0");
+            alglib.ap.assert(nvars>=1, "PCATruncatedSubspace: nvars<1");
+            alglib.ap.assert(nneeded>0, "PCATruncatedSubspace: nneeded<1");
+            alglib.ap.assert(nneeded<=nvars, "PCATruncatedSubspace: nneeded>nvars");
+            alglib.ap.assert(maxits>=0, "PCATruncatedSubspace: maxits<0");
+            alglib.ap.assert(math.isfinite(eps) && (double)(eps)>=(double)(0), "PCATruncatedSubspace: eps<0 or is not finite");
+            alglib.ap.assert(alglib.ap.rows(x)>=npoints, "PCATruncatedSubspace: rows(x)<npoints");
+            alglib.ap.assert(alglib.ap.cols(x)>=nvars || npoints==0, "PCATruncatedSubspace: cols(x)<nvars");
+            
+            //
+            // Special case: NPoints=0
+            //
+            if( npoints==0 )
+            {
+                s2 = new double[nneeded];
+                v = new double[nvars, nneeded];
+                for(i=0; i<=nvars-1; i++)
+                {
+                    s2[i] = 0;
+                }
+                for(i=0; i<=nvars-1; i++)
+                {
+                    for(j=0; j<=nneeded-1; j++)
+                    {
+                        if( i==j )
+                        {
+                            v[i,j] = 1;
+                        }
+                        else
+                        {
+                            v[i,j] = 0;
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Center matrix
+            //
+            means = new double[nvars];
+            for(i=0; i<=nvars-1; i++)
+            {
+                means[i] = 0;
+            }
+            vv = (double)1/(double)npoints;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    means[i_] = means[i_] + vv*x[i,i_];
+                }
+            }
+            a = new double[npoints, nvars];
+            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_] - means[i_];
+                }
+            }
+            
+            //
+            // Find eigenvalues with subspace iteration solver
+            //
+            evd.eigsubspacecreate(nvars, nneeded, solver, _params);
+            evd.eigsubspacesetcond(solver, eps, maxits, _params);
+            evd.eigsubspaceoocstart(solver, 0, _params);
+            while( evd.eigsubspaceooccontinue(solver, _params) )
+            {
+                alglib.ap.assert(solver.requesttype==0, "PCATruncatedSubspace: integrity check failed");
+                k = solver.requestsize;
+                apserv.rmatrixsetlengthatleast(ref b, npoints, k, _params);
+                ablas.rmatrixgemm(npoints, k, nvars, 1.0, a, 0, 0, 0, solver.x, 0, 0, 0, 0.0, b, 0, 0, _params);
+                ablas.rmatrixgemm(nvars, k, npoints, 1.0, a, 0, 0, 1, b, 0, 0, 0, 0.0, solver.ax, 0, 0, _params);
+            }
+            evd.eigsubspaceoocstop(solver, ref s2, ref v, rep, _params);
+            if( npoints!=1 )
+            {
+                for(i=0; i<=nneeded-1; i++)
+                {
+                    s2[i] = s2[i]/(npoints-1);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Sparse truncated principal components analysis
+
+        This function performs sparse truncated PCA, i.e. returns just a few  most
+        important principal components for a sparse input X.
+
+        Internally it uses iterative eigensolver which is very efficient when only
+        a minor fraction of full basis is required.
+
+        It should be noted that, unlike LDA, PCA does not use class labels.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            X           -   sparse dataset, sparse  npoints*nvars  matrix.  It  is
+                            recommended to use CRS sparse storage format;  non-CRS
+                            input will be internally converted to CRS.
+                            Matrix contains ONLY INDEPENDENT VARIABLES,  and  must
+                            be EXACTLY npoints*nvars.
+            NPoints     -   dataset size, NPoints>=0
+            NVars       -   number of independent variables, NVars>=1
+            NNeeded     -   number of requested components, in [1,NVars] range;
+                            this function is efficient only for NNeeded<<NVars.
+            Eps         -   desired  precision  of  vectors  returned;  underlying
+                            solver will stop iterations as soon as absolute  error
+                            in corresponding singular values  reduces  to  roughly
+                            eps*MAX(lambda[]), with lambda[] being array of  eigen
+                            values.
+                            Zero value means that  algorithm  performs  number  of
+                            iterations  specified  by  maxits  parameter,  without
+                            paying attention to precision.
+            MaxIts      -   number of iterations performed by  subspace  iteration
+                            method. Zero value means that no  limit  on  iteration
+                            count is placed (eps-based stopping condition is used).
+                            
+
+        OUTPUT PARAMETERS:
+            S2          -   array[NNeeded]. Variance values corresponding
+                            to basis vectors.
+            V           -   array[NVars,NNeeded]
+                            matrix, whose columns store basis vectors.
+                            
+        NOTE: passing eps=0 and maxits=0 results in small eps  being  selected  as
+              a stopping condition. Exact value of automatically selected  eps  is
+              version-dependent.
+
+        NOTE: zero  MaxIts  is  silently  replaced  by some reasonable value which
+              prevents eternal loops (possible when inputs are degenerate and  too
+              stringent stopping criteria are specified). In  current  version  it
+              is 50+2*NVars.
+
+          -- ALGLIB --
+             Copyright 10.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void pcatruncatedsubspacesparse(sparse.sparsematrix x,
+            int npoints,
+            int nvars,
+            int nneeded,
+            double eps,
+            int maxits,
+            ref double[] s2,
+            ref double[,] v,
+            alglib.xparams _params)
+        {
+            sparse.sparsematrix xcrs = new sparse.sparsematrix();
+            double[] b1 = new double[0];
+            double[] c1 = new double[0];
+            double[] z1 = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double vv = 0;
+            double[] means = new double[0];
+            evd.eigsubspacestate solver = new evd.eigsubspacestate();
+            evd.eigsubspacereport rep = new evd.eigsubspacereport();
+            int i_ = 0;
+
+            s2 = new double[0];
+            v = new double[0,0];
+
+            alglib.ap.assert(npoints>=0, "PCATruncatedSubspaceSparse: npoints<0");
+            alglib.ap.assert(nvars>=1, "PCATruncatedSubspaceSparse: nvars<1");
+            alglib.ap.assert(nneeded>0, "PCATruncatedSubspaceSparse: nneeded<1");
+            alglib.ap.assert(nneeded<=nvars, "PCATruncatedSubspaceSparse: nneeded>nvars");
+            alglib.ap.assert(maxits>=0, "PCATruncatedSubspaceSparse: maxits<0");
+            alglib.ap.assert(math.isfinite(eps) && (double)(eps)>=(double)(0), "PCATruncatedSubspaceSparse: eps<0 or is not finite");
+            if( npoints>0 )
+            {
+                alglib.ap.assert(sparse.sparsegetnrows(x, _params)==npoints, "PCATruncatedSubspaceSparse: rows(x)!=npoints");
+                alglib.ap.assert(sparse.sparsegetncols(x, _params)==nvars, "PCATruncatedSubspaceSparse: cols(x)!=nvars");
+            }
+            
+            //
+            // Special case: NPoints=0
+            //
+            if( npoints==0 )
+            {
+                s2 = new double[nneeded];
+                v = new double[nvars, nneeded];
+                for(i=0; i<=nvars-1; i++)
+                {
+                    s2[i] = 0;
+                }
+                for(i=0; i<=nvars-1; i++)
+                {
+                    for(j=0; j<=nneeded-1; j++)
+                    {
+                        if( i==j )
+                        {
+                            v[i,j] = 1;
+                        }
+                        else
+                        {
+                            v[i,j] = 0;
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // If input data are not in CRS format, perform conversion to CRS
+            //
+            if( !sparse.sparseiscrs(x, _params) )
+            {
+                sparse.sparsecopytocrs(x, xcrs, _params);
+                pcatruncatedsubspacesparse(xcrs, npoints, nvars, nneeded, eps, maxits, ref s2, ref v, _params);
+                return;
+            }
+            
+            //
+            // Initialize parameters, prepare buffers
+            //
+            b1 = new double[npoints];
+            z1 = new double[nvars];
+            if( (double)(eps)==(double)(0) && maxits==0 )
+            {
+                eps = 1.0E-6;
+            }
+            if( maxits==0 )
+            {
+                maxits = 50+2*nvars;
+            }
+            
+            //
+            // Calculate mean values
+            //
+            vv = (double)1/(double)npoints;
+            for(i=0; i<=npoints-1; i++)
+            {
+                b1[i] = vv;
+            }
+            sparse.sparsemtv(x, b1, ref means, _params);
+            
+            //
+            // Find eigenvalues with subspace iteration solver
+            //
+            evd.eigsubspacecreate(nvars, nneeded, solver, _params);
+            evd.eigsubspacesetcond(solver, eps, maxits, _params);
+            evd.eigsubspaceoocstart(solver, 0, _params);
+            while( evd.eigsubspaceooccontinue(solver, _params) )
+            {
+                alglib.ap.assert(solver.requesttype==0, "PCATruncatedSubspace: integrity check failed");
+                for(k=0; k<=solver.requestsize-1; k++)
+                {
+                    
+                    //
+                    // Calculate B1=(X-meansX)*Zk
+                    //
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        z1[i_] = solver.x[i_,k];
+                    }
+                    sparse.sparsemv(x, z1, ref b1, _params);
+                    vv = 0.0;
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        vv += solver.x[i_,k]*means[i_];
+                    }
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        b1[i] = b1[i]-vv;
+                    }
+                    
+                    //
+                    // Calculate (X-meansX)^T*B1
+                    //
+                    sparse.sparsemtv(x, b1, ref c1, _params);
+                    vv = 0;
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        vv = vv+b1[i];
+                    }
+                    for(j=0; j<=nvars-1; j++)
+                    {
+                        solver.ax[j,k] = c1[j]-vv*means[j];
+                    }
+                }
+            }
+            evd.eigsubspaceoocstop(solver, ref s2, ref v, rep, _params);
+            if( npoints!=1 )
+            {
+                for(i=0; i<=nneeded-1; i++)
+                {
+                    s2[i] = s2[i]/(npoints-1);
+                }
+            }
+        }
+
+
+    }
+    public class bdss
+    {
+        public class cvreport : apobject
+        {
+            public double relclserror;
+            public double avgce;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public cvreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                cvreport _result = new cvreport();
+                _result.relclserror = relclserror;
+                _result.avgce = avgce;
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.avgrelerror = avgrelerror;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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), _params);
+                cv = cv-xlny(pbl+pbk, (pbl+pbk)/(pal+pak+1+pbl+pbk), _params);
+                cv = cv-xlny(par-pak, (par-pak)/(par-pak+pbr-pbk+1), _params);
+                cv = cv-xlny(pbr-pbk, (pbr-pbk)/(par-pak+1+pbr-pbk), _params);
+                
+                //
+                // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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, _params);
+            }
+            bestcve = bestcve+getcv(cnt, nc, _params);
+            for(i=0; i<=nc-1; i++)
+            {
+                cnt[i] = 0;
+            }
+            for(i=j; i<=tiecount-1; i++)
+            {
+                tieaddc(c, ties, i, nc, ref cnt, _params);
+            }
+            bestcve = bestcve+getcv(cnt, nc, _params);
+            
+            //
+            // 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, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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, _params);
+                splits[0,j] = 0;
+                cv[0,j] = getcv(cnt, nc, _params);
+            }
+            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, _params);
+                    
+                    //
+                    // 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, _params);
+                    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, _params);
+                        
+                        //
+                        // Calculate CVE
+                        //
+                        cvtemp = cv[k-1,s-1]+getcv(cnt2, nc, _params);
+                        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, _params);
+                }
+                cve = cve+getcv(cnt, nc, _params);
+                for(i=0; i<=nc-1; i++)
+                {
+                    cnt[i] = 0;
+                }
+                for(i=j; i<=tiecount-1; i++)
+                {
+                    tieaddc(c, ties, i, nc, ref cnt, _params);
+                }
+                cve = cve+getcv(cnt, nc, _params);
+            }
+            else
+            {
+                
+                //
+                // General case: 2 or more intervals
+                //
+                // NOTE: we initialize both JL and JR (left and right bounds),
+                //       altough algorithm needs only JL.
+                //
+                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];
+                }
+                apserv.touchint(ref jr, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Internal function
+        *************************************************************************/
+        private static double xlny(double x,
+            double y,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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), _params);
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            for(i=ties[ntie]; i<=ties[ntie+1]-1; i++)
+            {
+                cnt[c[i]] = cnt[c[i]]-1;
+            }
+        }
+
+
+    }
+    public class mlpbase
+    {
+        /*************************************************************************
+        Model's errors:
+            * RelCLSError   -   fraction of misclassified cases.
+            * AvgCE         -   acerage cross-entropy
+            * RMSError      -   root-mean-square error
+            * AvgError      -   average error
+            * AvgRelError   -   average relative error
+            
+        NOTE 1: RelCLSError/AvgCE are zero on regression problems.
+
+        NOTE 2: on classification problems  RMSError/AvgError/AvgRelError  contain
+                errors in prediction of posterior probabilities
+        *************************************************************************/
+        public class modelerrors : apobject
+        {
+            public double relclserror;
+            public double avgce;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public modelerrors()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                modelerrors _result = new modelerrors();
+                _result.relclserror = relclserror;
+                _result.avgce = avgce;
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.avgrelerror = avgrelerror;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure is used to store MLP error and gradient.
+        *************************************************************************/
+        public class smlpgrad : apobject
+        {
+            public double f;
+            public double[] g;
+            public smlpgrad()
+            {
+                init();
+            }
+            public override void init()
+            {
+                g = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                smlpgrad _result = new smlpgrad();
+                _result.f = f;
+                _result.g = (double[])g.Clone();
+                return _result;
+            }
+        };
+
+
+        public class multilayerperceptron : apobject
+        {
+            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[,] xy;
+            public double[] xyrow;
+            public double[] nwbuf;
+            public int[] integerbuf;
+            public modelerrors err;
+            public double[] rndbuf;
+            public alglib.smp.shared_pool buf;
+            public alglib.smp.shared_pool gradbuf;
+            public double[,] dummydxy;
+            public sparse.sparsematrix dummysxy;
+            public int[] dummyidx;
+            public alglib.smp.shared_pool dummypool;
+            public multilayerperceptron()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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];
+                xy = new double[0,0];
+                xyrow = new double[0];
+                nwbuf = new double[0];
+                integerbuf = new int[0];
+                err = new modelerrors();
+                rndbuf = new double[0];
+                buf = new alglib.smp.shared_pool();
+                gradbuf = new alglib.smp.shared_pool();
+                dummydxy = new double[0,0];
+                dummysxy = new sparse.sparsematrix();
+                dummyidx = new int[0];
+                dummypool = new alglib.smp.shared_pool();
+            }
+            public override alglib.apobject make_copy()
+            {
+                multilayerperceptron _result = new multilayerperceptron();
+                _result.hlnetworktype = hlnetworktype;
+                _result.hlnormtype = hlnormtype;
+                _result.hllayersizes = (int[])hllayersizes.Clone();
+                _result.hlconnections = (int[])hlconnections.Clone();
+                _result.hlneurons = (int[])hlneurons.Clone();
+                _result.structinfo = (int[])structinfo.Clone();
+                _result.weights = (double[])weights.Clone();
+                _result.columnmeans = (double[])columnmeans.Clone();
+                _result.columnsigmas = (double[])columnsigmas.Clone();
+                _result.neurons = (double[])neurons.Clone();
+                _result.dfdnet = (double[])dfdnet.Clone();
+                _result.derror = (double[])derror.Clone();
+                _result.x = (double[])x.Clone();
+                _result.y = (double[])y.Clone();
+                _result.xy = (double[,])xy.Clone();
+                _result.xyrow = (double[])xyrow.Clone();
+                _result.nwbuf = (double[])nwbuf.Clone();
+                _result.integerbuf = (int[])integerbuf.Clone();
+                _result.err = (modelerrors)err.make_copy();
+                _result.rndbuf = (double[])rndbuf.Clone();
+                _result.buf = (alglib.smp.shared_pool)buf.make_copy();
+                _result.gradbuf = (alglib.smp.shared_pool)gradbuf.make_copy();
+                _result.dummydxy = (double[,])dummydxy.Clone();
+                _result.dummysxy = (sparse.sparsematrix)dummysxy.make_copy();
+                _result.dummyidx = (int[])dummyidx.Clone();
+                _result.dummypool = (alglib.smp.shared_pool)dummypool.make_copy();
+                return _result;
+            }
+        };
+
+
+
+
+        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 gradbasecasecost = 50000;
+        public const int microbatchsize = 64;
+
+
+        /*************************************************************************
+        This function returns number of weights  updates  which  is  required  for
+        gradient calculation problem to be splitted.
+        *************************************************************************/
+        public static int mlpgradsplitcost(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = gradbasecasecost;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This  function  returns  number  of elements in subset of dataset which is
+        required for gradient calculation problem to be splitted.
+        *************************************************************************/
+        public static int mlpgradsplitsize(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = microbatchsize;
+            return result;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(-5, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network, _params);
+            fillhighlevelinformation(network, nin, 0, 0, nout, false, true, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            addbiasedsummatorlayer(nhid, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(-5, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network, _params);
+            fillhighlevelinformation(network, nin, nhid, 0, nout, false, true, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            addbiasedsummatorlayer(nhid1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addbiasedsummatorlayer(nhid2, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(-5, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network, _params);
+            fillhighlevelinformation(network, nin, nhid1, nhid2, nout, false, true, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(3, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network, _params);
+            fillhighlevelinformation(network, nin, 0, 0, nout, false, false, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            addbiasedsummatorlayer(nhid, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(3, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network, _params);
+            fillhighlevelinformation(network, nin, nhid, 0, nout, false, false, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            addbiasedsummatorlayer(nhid1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addbiasedsummatorlayer(nhid2, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(3, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network, _params);
+            fillhighlevelinformation(network, nin, nhid1, nhid2, nout, false, false, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network, _params);
+            fillhighlevelinformation(network, nin, 0, 0, nout, false, false, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            addbiasedsummatorlayer(nhid, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network, _params);
+            fillhighlevelinformation(network, nin, nhid, 0, nout, false, false, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            addbiasedsummatorlayer(nhid1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addbiasedsummatorlayer(nhid2, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addbiasedsummatorlayer(nout, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, false, network, _params);
+            fillhighlevelinformation(network, nin, nhid1, nhid2, nout, false, false, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            addbiasedsummatorlayer(nout-1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addzerolayer(ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, true, network, _params);
+            fillhighlevelinformation(network, nin, 0, 0, nout, true, true, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            addbiasedsummatorlayer(nhid, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addbiasedsummatorlayer(nout-1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addzerolayer(ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, true, network, _params);
+            fillhighlevelinformation(network, nin, nhid, 0, nout, true, true, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            addbiasedsummatorlayer(nhid1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addbiasedsummatorlayer(nhid2, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addactivationlayer(1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addbiasedsummatorlayer(nout-1, ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            addzerolayer(ref lsizes, ref ltypes, ref lconnfirst, ref lconnlast, ref lastproc, _params);
+            
+            //
+            // Create
+            //
+            mlpcreate(nin, nout, lsizes, ltypes, lconnfirst, lconnlast, layerscount, true, network, _params);
+            fillhighlevelinformation(network, nin, nhid1, nhid2, nout, true, true, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            mlpcopyshared(network1, network2, _params);
+        }
+
+
+        /*************************************************************************
+        Copying of neural network (second parameter is passed as shared object).
+
+        INPUT PARAMETERS:
+            Network1 -   original
+
+        OUTPUT PARAMETERS:
+            Network2 -   copy
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcopyshared(multilayerperceptron network1,
+            multilayerperceptron network2,
+            alglib.xparams _params)
+        {
+            int wcount = 0;
+            int i = 0;
+            hpccores.mlpbuffers buf = new hpccores.mlpbuffers();
+            smlpgrad sgrad = new smlpgrad();
+
+            
+            //
+            // Copy scalar and array fields
+            //
+            network2.hlnetworktype = network1.hlnetworktype;
+            network2.hlnormtype = network1.hlnormtype;
+            apserv.copyintegerarray(network1.hllayersizes, ref network2.hllayersizes, _params);
+            apserv.copyintegerarray(network1.hlconnections, ref network2.hlconnections, _params);
+            apserv.copyintegerarray(network1.hlneurons, ref network2.hlneurons, _params);
+            apserv.copyintegerarray(network1.structinfo, ref network2.structinfo, _params);
+            apserv.copyrealarray(network1.weights, ref network2.weights, _params);
+            apserv.copyrealarray(network1.columnmeans, ref network2.columnmeans, _params);
+            apserv.copyrealarray(network1.columnsigmas, ref network2.columnsigmas, _params);
+            apserv.copyrealarray(network1.neurons, ref network2.neurons, _params);
+            apserv.copyrealarray(network1.dfdnet, ref network2.dfdnet, _params);
+            apserv.copyrealarray(network1.derror, ref network2.derror, _params);
+            apserv.copyrealarray(network1.x, ref network2.x, _params);
+            apserv.copyrealarray(network1.y, ref network2.y, _params);
+            apserv.copyrealarray(network1.nwbuf, ref network2.nwbuf, _params);
+            apserv.copyintegerarray(network1.integerbuf, ref network2.integerbuf, _params);
+            
+            //
+            // copy buffers
+            //
+            wcount = mlpgetweightscount(network1, _params);
+            alglib.smp.ae_shared_pool_set_seed(network2.buf, buf);
+            sgrad.g = new double[wcount];
+            sgrad.f = 0.0;
+            for(i=0; i<=wcount-1; i++)
+            {
+                sgrad.g[i] = 0.0;
+            }
+            alglib.smp.ae_shared_pool_set_seed(network2.gradbuf, sgrad);
+        }
+
+
+        /*************************************************************************
+        This function compares architectures of neural networks.  Only  geometries
+        are compared, weights and other parameters are not tested.
+
+          -- ALGLIB --
+             Copyright 20.06.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool mlpsamearchitecture(multilayerperceptron network1,
+            multilayerperceptron network2,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+            int ninfo = 0;
+
+            alglib.ap.assert(alglib.ap.len(network1.structinfo)>0 && alglib.ap.len(network1.structinfo)>=network1.structinfo[0], "MLPSameArchitecture: Network1 is uninitialized");
+            alglib.ap.assert(alglib.ap.len(network2.structinfo)>0 && alglib.ap.len(network2.structinfo)>=network2.structinfo[0], "MLPSameArchitecture: Network2 is uninitialized");
+            result = false;
+            if( network1.structinfo[0]!=network2.structinfo[0] )
+            {
+                return result;
+            }
+            ninfo = network1.structinfo[0];
+            for(i=0; i<=ninfo-1; i++)
+            {
+                if( network1.structinfo[i]!=network2.structinfo[i] )
+                {
+                    return result;
+                }
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function copies tunable  parameters (weights/means/sigmas)  from  one
+        network to another with same architecture. It  performs  some  rudimentary
+        checks that architectures are same, and throws exception if check fails.
+
+        It is intended for fast copying of states between two  network  which  are
+        known to have same geometry.
+
+        INPUT PARAMETERS:
+            Network1 -   source, must be correctly initialized
+            Network2 -   target, must have same architecture
+
+        OUTPUT PARAMETERS:
+            Network2 -   network state is copied from source to target
+
+          -- ALGLIB --
+             Copyright 20.06.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcopytunableparameters(multilayerperceptron network1,
+            multilayerperceptron network2,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int ninfo = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+
+            alglib.ap.assert(alglib.ap.len(network1.structinfo)>0 && alglib.ap.len(network1.structinfo)>=network1.structinfo[0], "MLPCopyTunableParameters: Network1 is uninitialized");
+            alglib.ap.assert(alglib.ap.len(network2.structinfo)>0 && alglib.ap.len(network2.structinfo)>=network2.structinfo[0], "MLPCopyTunableParameters: Network2 is uninitialized");
+            alglib.ap.assert(network1.structinfo[0]==network2.structinfo[0], "MLPCopyTunableParameters: Network1 geometry differs from that of Network2");
+            ninfo = network1.structinfo[0];
+            for(i=0; i<=ninfo-1; i++)
+            {
+                alglib.ap.assert(network1.structinfo[i]==network2.structinfo[i], "MLPCopyTunableParameters: Network1 geometry differs from that of Network2");
+            }
+            mlpproperties(network1, ref nin, ref nout, ref wcount, _params);
+            for(i=0; i<=wcount-1; i++)
+            {
+                network2.weights[i] = network1.weights[i];
+            }
+            if( mlpissoftmax(network1, _params) )
+            {
+                for(i=0; i<=nin-1; i++)
+                {
+                    network2.columnmeans[i] = network1.columnmeans[i];
+                    network2.columnsigmas[i] = network1.columnsigmas[i];
+                }
+            }
+            else
+            {
+                for(i=0; i<=nin+nout-1; i++)
+                {
+                    network2.columnmeans[i] = network1.columnmeans[i];
+                    network2.columnsigmas[i] = network1.columnsigmas[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This  function  exports  tunable   parameters  (weights/means/sigmas) from
+        network to contiguous array. Nothing is guaranteed about array format, the
+        only thing you can count for is that MLPImportTunableParameters() will  be
+        able to parse it.
+
+        It is intended for fast copying of states between network and backup array
+
+        INPUT PARAMETERS:
+            Network     -   source, must be correctly initialized
+            P           -   array to use. If its size is enough to store data,  it
+                            is reused.
+
+        OUTPUT PARAMETERS:
+            P           -   array which stores network parameters, resized if needed
+            PCount      -   number of parameters stored in array.
+
+          -- ALGLIB --
+             Copyright 20.06.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpexporttunableparameters(multilayerperceptron network,
+            ref double[] p,
+            ref int pcount,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+
+            pcount = 0;
+
+            alglib.ap.assert(alglib.ap.len(network.structinfo)>0 && alglib.ap.len(network.structinfo)>=network.structinfo[0], "MLPExportTunableParameters: Network is uninitialized");
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            if( mlpissoftmax(network, _params) )
+            {
+                pcount = wcount+2*nin;
+                apserv.rvectorsetlengthatleast(ref p, pcount, _params);
+                k = 0;
+                for(i=0; i<=wcount-1; i++)
+                {
+                    p[k] = network.weights[i];
+                    k = k+1;
+                }
+                for(i=0; i<=nin-1; i++)
+                {
+                    p[k] = network.columnmeans[i];
+                    k = k+1;
+                    p[k] = network.columnsigmas[i];
+                    k = k+1;
+                }
+            }
+            else
+            {
+                pcount = wcount+2*(nin+nout);
+                apserv.rvectorsetlengthatleast(ref p, pcount, _params);
+                k = 0;
+                for(i=0; i<=wcount-1; i++)
+                {
+                    p[k] = network.weights[i];
+                    k = k+1;
+                }
+                for(i=0; i<=nin+nout-1; i++)
+                {
+                    p[k] = network.columnmeans[i];
+                    k = k+1;
+                    p[k] = network.columnsigmas[i];
+                    k = k+1;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This  function imports  tunable   parameters  (weights/means/sigmas) which
+        were exported by MLPExportTunableParameters().
+
+        It is intended for fast copying of states between network and backup array
+
+        INPUT PARAMETERS:
+            Network     -   target:
+                            * must be correctly initialized
+                            * must have same geometry as network used to export params
+            P           -   array with parameters
+
+          -- ALGLIB --
+             Copyright 20.06.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpimporttunableparameters(multilayerperceptron network,
+            double[] p,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+
+            alglib.ap.assert(alglib.ap.len(network.structinfo)>0 && alglib.ap.len(network.structinfo)>=network.structinfo[0], "MLPImportTunableParameters: Network is uninitialized");
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            if( mlpissoftmax(network, _params) )
+            {
+                k = 0;
+                for(i=0; i<=wcount-1; i++)
+                {
+                    network.weights[i] = p[k];
+                    k = k+1;
+                }
+                for(i=0; i<=nin-1; i++)
+                {
+                    network.columnmeans[i] = p[k];
+                    k = k+1;
+                    network.columnsigmas[i] = p[k];
+                    k = k+1;
+                }
+            }
+            else
+            {
+                k = 0;
+                for(i=0; i<=wcount-1; i++)
+                {
+                    network.weights[i] = p[k];
+                    k = k+1;
+                }
+                for(i=0; i<=nin+nout-1; i++)
+                {
+                    network.columnmeans[i] = p[k];
+                    k = k+1;
+                    network.columnsigmas[i] = p[k];
+                    k = k+1;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int ssize = 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];
+            wcount = network.structinfo[4];
+            if( mlpissoftmax(network, _params) )
+            {
+                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,
+            alglib.xparams _params)
+        {
+            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.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,
+            alglib.xparams _params)
+        {
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int ntotal = 0;
+            int istart = 0;
+            hqrnd.hqrndstate r = new hqrnd.hqrndstate();
+            int entrysize = 0;
+            int entryoffs = 0;
+            int neuronidx = 0;
+            int neurontype = 0;
+            double vmean = 0;
+            double vvar = 0;
+            int i = 0;
+            int n1 = 0;
+            int n2 = 0;
+            double desiredsigma = 0;
+            int montecarlocnt = 0;
+            double ef = 0;
+            double ef2 = 0;
+            double v = 0;
+            double wscale = 0;
+
+            hqrnd.hqrndrandomize(r, _params);
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            desiredsigma = 0.5;
+            montecarlocnt = 20;
+            
+            //
+            // Stage 1:
+            // * Network.Weights is filled by standard deviation of weights
+            // * default values: sigma=1
+            //
+            for(i=0; i<=wcount-1; i++)
+            {
+                network.weights[i] = 1.0;
+            }
+            
+            //
+            // Stage 2:
+            // * assume that input neurons have zero mean and unit standard deviation
+            // * assume that constant neurons have zero standard deviation
+            // * perform forward pass along neurons
+            // * for each non-input non-constant neuron:
+            //   * calculate mean and standard deviation of neuron's output
+            //     assuming that we know means/deviations of neurons which feed it
+            //     and assuming that weights has unit variance and zero mean.
+            // * for each nonlinear neuron additionally we perform backward pass:
+            //   * scale variances of weights which feed it in such way that neuron's
+            //     input has unit standard deviation
+            //
+            // NOTE: this algorithm assumes that each connection feeds at most one
+            //       non-linear neuron. This assumption can be incorrect in upcoming
+            //       architectures with strong neurons. However, algorithm should
+            //       work smoothly even in this case.
+            //
+            // During this stage we use Network.RndBuf, which is grouped into NTotal
+            // entries, each of them having following format:
+            //
+            // Buf[Offset+0]        mean value of neuron's output
+            // Buf[Offset+1]        standard deviation of neuron's output
+            // 
+            //
+            //
+            entrysize = 2;
+            apserv.rvectorsetlengthatleast(ref network.rndbuf, entrysize*ntotal, _params);
+            for(neuronidx=0; neuronidx<=ntotal-1; neuronidx++)
+            {
+                neurontype = network.structinfo[istart+neuronidx*nfieldwidth+0];
+                entryoffs = entrysize*neuronidx;
+                if( neurontype==-2 )
+                {
+                    
+                    //
+                    // Input neuron: zero mean, unit variance.
+                    //
+                    network.rndbuf[entryoffs+0] = 0.0;
+                    network.rndbuf[entryoffs+1] = 1.0;
+                    continue;
+                }
+                if( neurontype==-3 )
+                {
+                    
+                    //
+                    // "-1" neuron: mean=-1, zero variance.
+                    //
+                    network.rndbuf[entryoffs+0] = -1.0;
+                    network.rndbuf[entryoffs+1] = 0.0;
+                    continue;
+                }
+                if( neurontype==-4 )
+                {
+                    
+                    //
+                    // "0" neuron: mean=0, zero variance.
+                    //
+                    network.rndbuf[entryoffs+0] = 0.0;
+                    network.rndbuf[entryoffs+1] = 0.0;
+                    continue;
+                }
+                if( neurontype==0 )
+                {
+                    
+                    //
+                    // Adaptive summator neuron:
+                    // * calculate its mean and variance.
+                    // * we assume that weights of this neuron have unit variance and zero mean.
+                    // * thus, neuron's output is always have zero mean
+                    // * as for variance, it is a bit more interesting:
+                    //   * let n[i] is i-th input neuron
+                    //   * let w[i] is i-th weight
+                    //   * we assume that n[i] and w[i] are independently distributed
+                    //   * Var(n0*w0+n1*w1+...) = Var(n0*w0)+Var(n1*w1)+...
+                    //   * Var(X*Y) = mean(X)^2*Var(Y) + mean(Y)^2*Var(X) + Var(X)*Var(Y)
+                    //   * mean(w[i])=0, var(w[i])=1
+                    //   * Var(n[i]*w[i]) = mean(n[i])^2 + Var(n[i])
+                    //
+                    n1 = network.structinfo[istart+neuronidx*nfieldwidth+2];
+                    n2 = n1+network.structinfo[istart+neuronidx*nfieldwidth+1]-1;
+                    vmean = 0.0;
+                    vvar = 0.0;
+                    for(i=n1; i<=n2; i++)
+                    {
+                        vvar = vvar+math.sqr(network.rndbuf[entrysize*i+0])+math.sqr(network.rndbuf[entrysize*i+1]);
+                    }
+                    network.rndbuf[entryoffs+0] = vmean;
+                    network.rndbuf[entryoffs+1] = Math.Sqrt(vvar);
+                    continue;
+                }
+                if( neurontype==-5 )
+                {
+                    
+                    //
+                    // Linear activation function
+                    //
+                    i = network.structinfo[istart+neuronidx*nfieldwidth+2];
+                    vmean = network.rndbuf[entrysize*i+0];
+                    vvar = math.sqr(network.rndbuf[entrysize*i+1]);
+                    if( (double)(vvar)>(double)(0) )
+                    {
+                        wscale = desiredsigma/Math.Sqrt(vvar);
+                    }
+                    else
+                    {
+                        wscale = 1.0;
+                    }
+                    randomizebackwardpass(network, i, wscale, _params);
+                    network.rndbuf[entryoffs+0] = vmean*wscale;
+                    network.rndbuf[entryoffs+1] = desiredsigma;
+                    continue;
+                }
+                if( neurontype>0 )
+                {
+                    
+                    //
+                    // Nonlinear activation function:
+                    // * scale its inputs
+                    // * estimate mean/sigma of its output using Monte-Carlo method
+                    //   (we simulate different inputs with unit deviation and
+                    //   sample activation function output on such inputs)
+                    //
+                    i = network.structinfo[istart+neuronidx*nfieldwidth+2];
+                    vmean = network.rndbuf[entrysize*i+0];
+                    vvar = math.sqr(network.rndbuf[entrysize*i+1]);
+                    if( (double)(vvar)>(double)(0) )
+                    {
+                        wscale = desiredsigma/Math.Sqrt(vvar);
+                    }
+                    else
+                    {
+                        wscale = 1.0;
+                    }
+                    randomizebackwardpass(network, i, wscale, _params);
+                    ef = 0.0;
+                    ef2 = 0.0;
+                    vmean = vmean*wscale;
+                    for(i=0; i<=montecarlocnt-1; i++)
+                    {
+                        v = vmean+desiredsigma*hqrnd.hqrndnormal(r, _params);
+                        ef = ef+v;
+                        ef2 = ef2+v*v;
+                    }
+                    ef = ef/montecarlocnt;
+                    ef2 = ef2/montecarlocnt;
+                    network.rndbuf[entryoffs+0] = ef;
+                    network.rndbuf[entryoffs+1] = Math.Max(ef2-ef*ef, 0.0);
+                    continue;
+                }
+                alglib.ap.assert(false, "MLPRandomize: unexpected neuron type");
+            }
+            
+            //
+            // Stage 3: generate weights.
+            //
+            for(i=0; i<=wcount-1; i++)
+            {
+                network.weights[i] = network.weights[i]*hqrnd.hqrndnormal(r, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Randomization of neural network weights and standartisator
+
+          -- ALGLIB --
+             Copyright 10.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlprandomizefull(multilayerperceptron network,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            
+            //
+            // Process network
+            //
+            mlprandomize(network, _params);
+            for(i=0; i<=nin-1; i++)
+            {
+                network.columnmeans[i] = math.randomreal()-0.5;
+                network.columnsigmas[i] = math.randomreal()+0.5;
+            }
+            if( !mlpissoftmax(network, _params) )
+            {
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            
+            //
+            // Means/Sigmas
+            //
+            if( mlpissoftmax(network, _params) )
+            {
+                jmax = nin-1;
+            }
+            else
+            {
+                jmax = nin+nout-1;
+            }
+            means = new double[jmax+1];
+            sigmas = new double[jmax+1];
+            for(i=0; i<=jmax; i++)
+            {
+                means[i] = 0;
+                sigmas[i] = 0;
+            }
+            for(i=0; i<=ssize-1; i++)
+            {
+                for(j=0; j<=jmax; j++)
+                {
+                    means[j] = means[j]+xy[i,j];
+                }
+            }
+            for(i=0; i<=jmax; i++)
+            {
+                means[i] = means[i]/ssize;
+            }
+            for(i=0; i<=ssize-1; i++)
+            {
+                for(j=0; j<=jmax; j++)
+                {
+                    sigmas[j] = sigmas[j]+math.sqr(xy[i,j]-means[j]);
+                }
+            }
+            for(i=0; i<=jmax; i++)
+            {
+                sigmas[i] = Math.Sqrt(sigmas[i]/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, _params) )
+            {
+                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;
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine.
+        Initialization for preprocessor based on a sample.
+
+        INPUT
+            Network -   initialized neural network;
+            XY      -   sample, given by sparse matrix;
+            SSize   -   sample size.
+
+        OUTPUT
+            Network -   neural network with initialised preprocessor.
+
+          -- ALGLIB --
+             Copyright 26.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpinitpreprocessorsparse(multilayerperceptron network,
+            sparse.sparsematrix xy,
+            int ssize,
+            alglib.xparams _params)
+        {
+            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;
+            int i = 0;
+            int j = 0;
+
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            
+            //
+            // Means/Sigmas
+            //
+            if( mlpissoftmax(network, _params) )
+            {
+                jmax = nin-1;
+            }
+            else
+            {
+                jmax = nin+nout-1;
+            }
+            means = new double[jmax+1];
+            sigmas = new double[jmax+1];
+            for(i=0; i<=jmax; i++)
+            {
+                means[i] = 0;
+                sigmas[i] = 0;
+            }
+            for(i=0; i<=ssize-1; i++)
+            {
+                sparse.sparsegetrow(xy, i, ref network.xyrow, _params);
+                for(j=0; j<=jmax; j++)
+                {
+                    means[j] = means[j]+network.xyrow[j];
+                }
+            }
+            for(i=0; i<=jmax; i++)
+            {
+                means[i] = means[i]/ssize;
+            }
+            for(i=0; i<=ssize-1; i++)
+            {
+                sparse.sparsegetrow(xy, i, ref network.xyrow, _params);
+                for(j=0; j<=jmax; j++)
+                {
+                    sigmas[j] = sigmas[j]+math.sqr(network.xyrow[j]-means[j]);
+                }
+            }
+            for(i=0; i<=jmax; i++)
+            {
+                sigmas[i] = Math.Sqrt(sigmas[i]/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, _params) )
+            {
+                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;
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine.
+        Initialization for preprocessor based on a subsample.
+
+        INPUT PARAMETERS:
+            Network -   network initialized with one of the network creation funcs
+            XY      -   original dataset; one sample = one row;
+                        first NIn columns contain inputs,
+                        next NOut columns - desired outputs.
+            SetSize -   real size of XY, SetSize>=0;
+            Idx     -   subset of SubsetSize elements, array[SubsetSize]:
+                        * Idx[I] stores row index in the original dataset which is
+                          given by XY. Gradient is calculated with respect to rows
+                          whose indexes are stored in Idx[].
+                        * Idx[]  must store correct indexes; this function  throws
+                          an  exception  in  case  incorrect index (less than 0 or
+                          larger than rows(XY)) is given
+                        * Idx[]  may  store  indexes  in  any  order and even with
+                          repetitions.
+            SubsetSize- number of elements in Idx[] array.
+
+        OUTPUT:
+            Network -   neural network with initialised preprocessor.
+            
+        NOTE: when  SubsetSize<0 is used full dataset by call MLPInitPreprocessor
+              function.
+
+          -- ALGLIB --
+             Copyright 23.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpinitpreprocessorsubset(multilayerperceptron network,
+            double[,] xy,
+            int setsize,
+            int[] idx,
+            int subsetsize,
+            alglib.xparams _params)
+        {
+            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;
+            int npoints = 0;
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(setsize>=0, "MLPInitPreprocessorSubset: SetSize<0");
+            if( subsetsize<0 )
+            {
+                mlpinitpreprocessor(network, xy, setsize, _params);
+                return;
+            }
+            alglib.ap.assert(subsetsize<=alglib.ap.len(idx), "MLPInitPreprocessorSubset: SubsetSize>Length(Idx)");
+            npoints = setsize;
+            for(i=0; i<=subsetsize-1; i++)
+            {
+                alglib.ap.assert(idx[i]>=0, "MLPInitPreprocessorSubset: incorrect index of XY row(Idx[I]<0)");
+                alglib.ap.assert(idx[i]<=npoints-1, "MLPInitPreprocessorSubset: incorrect index of XY row(Idx[I]>Rows(XY)-1)");
+            }
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            
+            //
+            // Means/Sigmas
+            //
+            if( mlpissoftmax(network, _params) )
+            {
+                jmax = nin-1;
+            }
+            else
+            {
+                jmax = nin+nout-1;
+            }
+            means = new double[jmax+1];
+            sigmas = new double[jmax+1];
+            for(i=0; i<=jmax; i++)
+            {
+                means[i] = 0;
+                sigmas[i] = 0;
+            }
+            for(i=0; i<=subsetsize-1; i++)
+            {
+                for(j=0; j<=jmax; j++)
+                {
+                    means[j] = means[j]+xy[idx[i],j];
+                }
+            }
+            for(i=0; i<=jmax; i++)
+            {
+                means[i] = means[i]/subsetsize;
+            }
+            for(i=0; i<=subsetsize-1; i++)
+            {
+                for(j=0; j<=jmax; j++)
+                {
+                    sigmas[j] = sigmas[j]+math.sqr(xy[idx[i],j]-means[j]);
+                }
+            }
+            for(i=0; i<=jmax; i++)
+            {
+                sigmas[i] = Math.Sqrt(sigmas[i]/subsetsize);
+            }
+            
+            //
+            // 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, _params) )
+            {
+                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;
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine.
+        Initialization for preprocessor based on a subsample.
+
+        INPUT PARAMETERS:
+            Network -   network initialized with one of the network creation funcs
+            XY      -   original dataset, given by sparse matrix;
+                        one sample = one row;
+                        first NIn columns contain inputs,
+                        next NOut columns - desired outputs.
+            SetSize -   real size of XY, SetSize>=0;
+            Idx     -   subset of SubsetSize elements, array[SubsetSize]:
+                        * Idx[I] stores row index in the original dataset which is
+                          given by XY. Gradient is calculated with respect to rows
+                          whose indexes are stored in Idx[].
+                        * Idx[]  must store correct indexes; this function  throws
+                          an  exception  in  case  incorrect index (less than 0 or
+                          larger than rows(XY)) is given
+                        * Idx[]  may  store  indexes  in  any  order and even with
+                          repetitions.
+            SubsetSize- number of elements in Idx[] array.
+            
+        OUTPUT:
+            Network -   neural network with initialised preprocessor.
+            
+        NOTE: when SubsetSize<0 is used full dataset by call
+              MLPInitPreprocessorSparse function.
+              
+          -- ALGLIB --
+             Copyright 26.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpinitpreprocessorsparsesubset(multilayerperceptron network,
+            sparse.sparsematrix xy,
+            int setsize,
+            int[] idx,
+            int subsetsize,
+            alglib.xparams _params)
+        {
+            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;
+            int npoints = 0;
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(setsize>=0, "MLPInitPreprocessorSparseSubset: SetSize<0");
+            if( subsetsize<0 )
+            {
+                mlpinitpreprocessorsparse(network, xy, setsize, _params);
+                return;
+            }
+            alglib.ap.assert(subsetsize<=alglib.ap.len(idx), "MLPInitPreprocessorSparseSubset: SubsetSize>Length(Idx)");
+            npoints = setsize;
+            for(i=0; i<=subsetsize-1; i++)
+            {
+                alglib.ap.assert(idx[i]>=0, "MLPInitPreprocessorSparseSubset: incorrect index of XY row(Idx[I]<0)");
+                alglib.ap.assert(idx[i]<=npoints-1, "MLPInitPreprocessorSparseSubset: incorrect index of XY row(Idx[I]>Rows(XY)-1)");
+            }
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            
+            //
+            // Means/Sigmas
+            //
+            if( mlpissoftmax(network, _params) )
+            {
+                jmax = nin-1;
+            }
+            else
+            {
+                jmax = nin+nout-1;
+            }
+            means = new double[jmax+1];
+            sigmas = new double[jmax+1];
+            for(i=0; i<=jmax; i++)
+            {
+                means[i] = 0;
+                sigmas[i] = 0;
+            }
+            for(i=0; i<=subsetsize-1; i++)
+            {
+                sparse.sparsegetrow(xy, idx[i], ref network.xyrow, _params);
+                for(j=0; j<=jmax; j++)
+                {
+                    means[j] = means[j]+network.xyrow[j];
+                }
+            }
+            for(i=0; i<=jmax; i++)
+            {
+                means[i] = means[i]/subsetsize;
+            }
+            for(i=0; i<=subsetsize-1; i++)
+            {
+                sparse.sparsegetrow(xy, idx[i], ref network.xyrow, _params);
+                for(j=0; j<=jmax; j++)
+                {
+                    sigmas[j] = sigmas[j]+math.sqr(network.xyrow[j]-means[j]);
+                }
+            }
+            for(i=0; i<=jmax; i++)
+            {
+                sigmas[i] = Math.Sqrt(sigmas[i]/subsetsize);
+            }
+            
+            //
+            // 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, _params) )
+            {
+                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,
+            alglib.xparams _params)
+        {
+            nin = 0;
+            nout = 0;
+            wcount = 0;
+
+            nin = network.structinfo[1];
+            nout = network.structinfo[2];
+            wcount = network.structinfo[4];
+        }
+
+
+        /*************************************************************************
+        Returns number of "internal", low-level neurons in the network (one  which
+        is stored in StructInfo).
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static int mlpntotal(multilayerperceptron network,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = network.structinfo[3];
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns number of inputs.
+
+          -- ALGLIB --
+             Copyright 19.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static int mlpgetinputscount(multilayerperceptron network,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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-f*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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        '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,
+            alglib.xparams _params)
+        {
+            y = new double[0];
+
+            mlpprocess(network, x, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        Error of the neural network on dataset.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network;
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format;
+            NPoints     -   points count.
+
+        RESULT:
+            sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2)
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlperror(multilayerperceptron network,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "MLPError: XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+1, "MLPError: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPError: XY has less than NIn+NOut columns");
+                }
+            }
+            mlpallerrorsx(network, xy, network.dummysxy, npoints, 0, network.dummyidx, 0, npoints, 0, network.buf, network.err, _params);
+            result = math.sqr(network.err.rmserror)*npoints*mlpgetoutputscount(network, _params)/2;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Error of the neural network on dataset given by sparse matrix.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format. This function checks  correctness
+                            of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                            correct) and throws exception when  incorrect  dataset
+                            is passed.  Sparse  matrix  must  use  CRS  format for
+                            storage.
+            NPoints     -   points count, >=0
+
+        RESULT:
+            sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2)
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+          
+          -- ALGLIB --
+             Copyright 23.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlperrorsparse(multilayerperceptron network,
+            sparse.sparsematrix xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(sparse.sparseiscrs(xy, _params), "MLPErrorSparse: XY is not in CRS format.");
+            alglib.ap.assert(sparse.sparsegetnrows(xy, _params)>=npoints, "MLPErrorSparse: XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+1, "MLPErrorSparse: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPErrorSparse: XY has less than NIn+NOut columns");
+                }
+            }
+            mlpallerrorsx(network, network.dummydxy, xy, npoints, 1, network.dummyidx, 0, npoints, 0, network.buf, network.err, _params);
+            result = math.sqr(network.err.rmserror)*npoints*mlpgetoutputscount(network, _params)/2;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Natural error function for neural network, internal subroutine.
+
+        NOTE: this function is single-threaded. Unlike other  error  function,  it
+        receives no speed-up from being executed in SMP mode.
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlperrorn(multilayerperceptron network,
+            double[,] xy,
+            int ssize,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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, _params);
+                
+                //
+                // 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], _params);
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Classification error of the neural network on dataset.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network;
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format;
+            NPoints     -   points count.
+
+        RESULT:
+            classification error (number of misclassified cases)
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static int mlpclserror(multilayerperceptron network,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "MLPClsError: XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+1, "MLPClsError: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPClsError: XY has less than NIn+NOut columns");
+                }
+            }
+            mlpallerrorsx(network, xy, network.dummysxy, npoints, 0, network.dummyidx, 0, npoints, 0, network.buf, network.err, _params);
+            result = (int)Math.Round(npoints*network.err.relclserror);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Relative classification error on the test set.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network;
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format;
+            NPoints     -   points count.
+
+        RESULT:
+        Percent   of incorrectly   classified  cases.  Works  both  for classifier
+        networks and general purpose networks used as classifiers.
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+
+          -- ALGLIB --
+             Copyright 25.12.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlprelclserror(multilayerperceptron network,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "MLPRelClsError: XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+1, "MLPRelClsError: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPRelClsError: XY has less than NIn+NOut columns");
+                }
+            }
+            if( npoints>0 )
+            {
+                result = (double)mlpclserror(network, xy, npoints, _params)/(double)npoints;
+            }
+            else
+            {
+                result = 0.0;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Relative classification error on the test set given by sparse matrix.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network;
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format. Sparse matrix must use CRS format
+                            for storage.
+            NPoints     -   points count, >=0.
+
+        RESULT:
+        Percent   of incorrectly   classified  cases.  Works  both  for classifier
+        networks and general purpose networks used as classifiers.
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+          
+          -- ALGLIB --
+             Copyright 09.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlprelclserrorsparse(multilayerperceptron network,
+            sparse.sparsematrix xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(sparse.sparseiscrs(xy, _params), "MLPRelClsErrorSparse: sparse matrix XY is not in CRS format.");
+            alglib.ap.assert(sparse.sparsegetnrows(xy, _params)>=npoints, "MLPRelClsErrorSparse: sparse matrix XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+1, "MLPRelClsErrorSparse: sparse matrix XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPRelClsErrorSparse: sparse matrix XY has less than NIn+NOut columns");
+                }
+            }
+            mlpallerrorsx(network, network.dummydxy, xy, npoints, 1, network.dummyidx, 0, npoints, 0, network.buf, network.err, _params);
+            result = network.err.relclserror;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average cross-entropy  (in bits  per element) on the test set.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network;
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format;
+            NPoints     -   points count.
+
+        RESULT:
+        CrossEntropy/(NPoints*LN(2)).
+        Zero if network solves regression task.
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+
+          -- ALGLIB --
+             Copyright 08.01.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpavgce(multilayerperceptron network,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "MLPAvgCE: XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+1, "MLPAvgCE: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPAvgCE: XY has less than NIn+NOut columns");
+                }
+            }
+            mlpallerrorsx(network, xy, network.dummysxy, npoints, 0, network.dummyidx, 0, npoints, 0, network.buf, network.err, _params);
+            result = network.err.avgce;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average  cross-entropy  (in bits  per element)  on the  test set  given by
+        sparse matrix.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network;
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format. This function checks  correctness
+                            of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                            correct) and throws exception when  incorrect  dataset
+                            is passed.  Sparse  matrix  must  use  CRS  format for
+                            storage.
+            NPoints     -   points count, >=0.
+
+        RESULT:
+        CrossEntropy/(NPoints*LN(2)).
+        Zero if network solves regression task.
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+          
+          -- ALGLIB --
+             Copyright 9.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpavgcesparse(multilayerperceptron network,
+            sparse.sparsematrix xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(sparse.sparseiscrs(xy, _params), "MLPAvgCESparse: sparse matrix XY is not in CRS format.");
+            alglib.ap.assert(sparse.sparsegetnrows(xy, _params)>=npoints, "MLPAvgCESparse: sparse matrix XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+1, "MLPAvgCESparse: sparse matrix XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPAvgCESparse: sparse matrix XY has less than NIn+NOut columns");
+                }
+            }
+            mlpallerrorsx(network, network.dummydxy, xy, npoints, 1, network.dummyidx, 0, npoints, 0, network.buf, network.err, _params);
+            result = network.err.avgce;
+            return result;
+        }
+
+
+        /*************************************************************************
+        RMS error on the test set given.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network;
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format;
+            NPoints     -   points count.
+
+        RESULT:
+        Root mean  square error. Its meaning for regression task is obvious. As for
+        classification  task,  RMS  error  means  error  when estimating  posterior
+        probabilities.
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+
+          -- ALGLIB --
+             Copyright 04.11.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlprmserror(multilayerperceptron network,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "MLPRMSError: XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+1, "MLPRMSError: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPRMSError: XY has less than NIn+NOut columns");
+                }
+            }
+            mlpallerrorsx(network, xy, network.dummysxy, npoints, 0, network.dummyidx, 0, npoints, 0, network.buf, network.err, _params);
+            result = network.err.rmserror;
+            return result;
+        }
+
+
+        /*************************************************************************
+        RMS error on the test set given by sparse matrix.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network;
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format. This function checks  correctness
+                            of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                            correct) and throws exception when  incorrect  dataset
+                            is passed.  Sparse  matrix  must  use  CRS  format for
+                            storage.
+            NPoints     -   points count, >=0.
+
+        RESULT:
+        Root mean  square error. Its meaning for regression task is obvious. As for
+        classification  task,  RMS  error  means  error  when estimating  posterior
+        probabilities.
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+          
+          -- ALGLIB --
+             Copyright 09.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlprmserrorsparse(multilayerperceptron network,
+            sparse.sparsematrix xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(sparse.sparseiscrs(xy, _params), "MLPRMSErrorSparse: sparse matrix XY is not in CRS format.");
+            alglib.ap.assert(sparse.sparsegetnrows(xy, _params)>=npoints, "MLPRMSErrorSparse: sparse matrix XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+1, "MLPRMSErrorSparse: sparse matrix XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPRMSErrorSparse: sparse matrix XY has less than NIn+NOut columns");
+                }
+            }
+            mlpallerrorsx(network, network.dummydxy, xy, npoints, 1, network.dummyidx, 0, npoints, 0, network.buf, network.err, _params);
+            result = network.err.rmserror;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average absolute error on the test set.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network;
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format;
+            NPoints     -   points count.
+
+        RESULT:
+        Its meaning for regression task is obvious. As for classification task, it
+        means average error when estimating posterior probabilities.
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+
+          -- ALGLIB --
+             Copyright 11.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpavgerror(multilayerperceptron network,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "MLPAvgError: XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+1, "MLPAvgError: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPAvgError: XY has less than NIn+NOut columns");
+                }
+            }
+            mlpallerrorsx(network, xy, network.dummysxy, npoints, 0, network.dummyidx, 0, npoints, 0, network.buf, network.err, _params);
+            result = network.err.avgerror;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average absolute error on the test set given by sparse matrix.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network;
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format. This function checks  correctness
+                            of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                            correct) and throws exception when  incorrect  dataset
+                            is passed.  Sparse  matrix  must  use  CRS  format for
+                            storage.
+            NPoints     -   points count, >=0.
+
+        RESULT:
+        Its meaning for regression task is obvious. As for classification task, it
+        means average error when estimating posterior probabilities.
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+          
+          -- ALGLIB --
+             Copyright 09.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpavgerrorsparse(multilayerperceptron network,
+            sparse.sparsematrix xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(sparse.sparseiscrs(xy, _params), "MLPAvgErrorSparse: XY is not in CRS format.");
+            alglib.ap.assert(sparse.sparsegetnrows(xy, _params)>=npoints, "MLPAvgErrorSparse: XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+1, "MLPAvgErrorSparse: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPAvgErrorSparse: XY has less than NIn+NOut columns");
+                }
+            }
+            mlpallerrorsx(network, network.dummydxy, xy, npoints, 1, network.dummyidx, 0, npoints, 0, network.buf, network.err, _params);
+            result = network.err.avgerror;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average relative error on the test set.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network;
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format;
+            NPoints     -   points count.
+
+        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.
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+
+          -- ALGLIB --
+             Copyright 11.03.2008 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpavgrelerror(multilayerperceptron network,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "MLPAvgRelError: XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+1, "MLPAvgRelError: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPAvgRelError: XY has less than NIn+NOut columns");
+                }
+            }
+            mlpallerrorsx(network, xy, network.dummysxy, npoints, 0, network.dummyidx, 0, npoints, 0, network.buf, network.err, _params);
+            result = network.err.avgrelerror;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average relative error on the test set given by sparse matrix.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network     -   neural network;
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format. This function checks  correctness
+                            of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                            correct) and throws exception when  incorrect  dataset
+                            is passed.  Sparse  matrix  must  use  CRS  format for
+                            storage.
+            NPoints     -   points count, >=0.
+
+        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.
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+          
+          -- ALGLIB --
+             Copyright 09.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlpavgrelerrorsparse(multilayerperceptron network,
+            sparse.sparsematrix xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(sparse.sparseiscrs(xy, _params), "MLPAvgRelErrorSparse: XY is not in CRS format.");
+            alglib.ap.assert(sparse.sparsegetnrows(xy, _params)>=npoints, "MLPAvgRelErrorSparse: XY has less than NPoints rows");
+            if( npoints>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+1, "MLPAvgRelErrorSparse: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPAvgRelErrorSparse: XY has less than NIn+NOut columns");
+                }
+            }
+            mlpallerrorsx(network, network.dummydxy, xy, npoints, 1, network.dummyidx, 0, npoints, 0, network.buf, network.err, _params);
+            result = network.err.avgrelerror;
+            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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int nout = 0;
+            int ntotal = 0;
+
+            e = 0;
+
+            
+            //
+            // Alloc
+            //
+            apserv.rvectorsetlengthatleast(ref grad, network.structinfo[4], _params);
+            
+            //
+            // Prepare dError/dOut, internal structures
+            //
+            mlpprocess(network, x, ref network.y, _params);
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            double s = 0;
+            int i = 0;
+            int nout = 0;
+            int ntotal = 0;
+
+            e = 0;
+
+            
+            //
+            // Alloc
+            //
+            apserv.rvectorsetlengthatleast(ref grad, network.structinfo[4], _params);
+            
+            //
+            // Prepare dError/dOut, internal structures
+            //
+            mlpprocess(network, x, ref network.y, _params);
+            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], _params);
+                }
+            }
+            
+            //
+            // gradient
+            //
+            mlpinternalcalculategradient(network, network.neurons, network.weights, ref network.derror, ref grad, true, _params);
+        }
+
+
+        /*************************************************************************
+        Batch gradient calculation for a set of inputs/outputs
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network -   network initialized with one of the network creation funcs
+            XY      -   original dataset in dense format; one sample = one row:
+                        * first NIn columns contain inputs,
+                        * for regression problem, next NOut columns store
+                          desired outputs.
+                        * for classification problem, next column (just one!)
+                          stores class number.
+            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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int subset0 = 0;
+            int subset1 = 0;
+            int subsettype = 0;
+            smlpgrad sgrad = null;
+
+            e = 0;
+
+            alglib.ap.assert(ssize>=0, "MLPGradBatchSparse: SSize<0");
+            subset0 = 0;
+            subset1 = ssize;
+            subsettype = 0;
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            apserv.rvectorsetlengthatleast(ref grad, wcount, _params);
+            alglib.smp.ae_shared_pool_first_recycled(network.gradbuf, ref sgrad);
+            while( sgrad!=null )
+            {
+                sgrad.f = 0.0;
+                for(i=0; i<=wcount-1; i++)
+                {
+                    sgrad.g[i] = 0.0;
+                }
+                alglib.smp.ae_shared_pool_next_recycled(network.gradbuf, ref sgrad);
+            }
+            mlpgradbatchx(network, xy, network.dummysxy, ssize, 0, network.dummyidx, subset0, subset1, subsettype, network.buf, network.gradbuf, _params);
+            e = 0.0;
+            for(i=0; i<=wcount-1; i++)
+            {
+                grad[i] = 0.0;
+            }
+            alglib.smp.ae_shared_pool_first_recycled(network.gradbuf, ref sgrad);
+            while( sgrad!=null )
+            {
+                e = e+sgrad.f;
+                for(i=0; i<=wcount-1; i++)
+                {
+                    grad[i] = grad[i]+sgrad.g[i];
+                }
+                alglib.smp.ae_shared_pool_next_recycled(network.gradbuf, ref sgrad);
+            }
+        }
+
+
+        /*************************************************************************
+        Batch gradient calculation for a set  of inputs/outputs  given  by  sparse
+        matrices
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network -   network initialized with one of the network creation funcs
+            XY      -   original dataset in sparse format; one sample = one row:
+                        * MATRIX MUST BE STORED IN CRS FORMAT
+                        * first NIn columns contain inputs.
+                        * for regression problem, next NOut columns store
+                          desired outputs.
+                        * for classification problem, next column (just one!)
+                          stores class number.
+            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 26.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpgradbatchsparse(multilayerperceptron network,
+            sparse.sparsematrix xy,
+            int ssize,
+            ref double e,
+            ref double[] grad,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int subset0 = 0;
+            int subset1 = 0;
+            int subsettype = 0;
+            smlpgrad sgrad = null;
+
+            e = 0;
+
+            alglib.ap.assert(ssize>=0, "MLPGradBatchSparse: SSize<0");
+            alglib.ap.assert(sparse.sparseiscrs(xy, _params), "MLPGradBatchSparse: sparse matrix XY must be in CRS format.");
+            subset0 = 0;
+            subset1 = ssize;
+            subsettype = 0;
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            apserv.rvectorsetlengthatleast(ref grad, wcount, _params);
+            alglib.smp.ae_shared_pool_first_recycled(network.gradbuf, ref sgrad);
+            while( sgrad!=null )
+            {
+                sgrad.f = 0.0;
+                for(i=0; i<=wcount-1; i++)
+                {
+                    sgrad.g[i] = 0.0;
+                }
+                alglib.smp.ae_shared_pool_next_recycled(network.gradbuf, ref sgrad);
+            }
+            mlpgradbatchx(network, network.dummydxy, xy, ssize, 1, network.dummyidx, subset0, subset1, subsettype, network.buf, network.gradbuf, _params);
+            e = 0.0;
+            for(i=0; i<=wcount-1; i++)
+            {
+                grad[i] = 0.0;
+            }
+            alglib.smp.ae_shared_pool_first_recycled(network.gradbuf, ref sgrad);
+            while( sgrad!=null )
+            {
+                e = e+sgrad.f;
+                for(i=0; i<=wcount-1; i++)
+                {
+                    grad[i] = grad[i]+sgrad.g[i];
+                }
+                alglib.smp.ae_shared_pool_next_recycled(network.gradbuf, ref sgrad);
+            }
+        }
+
+
+        /*************************************************************************
+        Batch gradient calculation for a subset of dataset
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network -   network initialized with one of the network creation funcs
+            XY      -   original dataset in dense format; one sample = one row:
+                        * first NIn columns contain inputs,
+                        * for regression problem, next NOut columns store
+                          desired outputs.
+                        * for classification problem, next column (just one!)
+                          stores class number.
+            SetSize -   real size of XY, SetSize>=0;
+            Idx     -   subset of SubsetSize elements, array[SubsetSize]:
+                        * Idx[I] stores row index in the original dataset which is
+                          given by XY. Gradient is calculated with respect to rows
+                          whose indexes are stored in Idx[].
+                        * Idx[]  must store correct indexes; this function  throws
+                          an  exception  in  case  incorrect index (less than 0 or
+                          larger than rows(XY)) is given
+                        * Idx[]  may  store  indexes  in  any  order and even with
+                          repetitions.
+            SubsetSize- number of elements in Idx[] array:
+                        * positive value means that subset given by Idx[] is processed
+                        * zero value results in zero gradient
+                        * negative value means that full dataset is processed
+            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 26.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpgradbatchsubset(multilayerperceptron network,
+            double[,] xy,
+            int setsize,
+            int[] idx,
+            int subsetsize,
+            ref double e,
+            ref double[] grad,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int npoints = 0;
+            int subset0 = 0;
+            int subset1 = 0;
+            int subsettype = 0;
+            smlpgrad sgrad = null;
+
+            e = 0;
+
+            alglib.ap.assert(setsize>=0, "MLPGradBatchSubset: SetSize<0");
+            alglib.ap.assert(subsetsize<=alglib.ap.len(idx), "MLPGradBatchSubset: SubsetSize>Length(Idx)");
+            npoints = setsize;
+            if( subsetsize<0 )
+            {
+                subset0 = 0;
+                subset1 = setsize;
+                subsettype = 0;
+            }
+            else
+            {
+                subset0 = 0;
+                subset1 = subsetsize;
+                subsettype = 1;
+                for(i=0; i<=subsetsize-1; i++)
+                {
+                    alglib.ap.assert(idx[i]>=0, "MLPGradBatchSubset: incorrect index of XY row(Idx[I]<0)");
+                    alglib.ap.assert(idx[i]<=npoints-1, "MLPGradBatchSubset: incorrect index of XY row(Idx[I]>Rows(XY)-1)");
+                }
+            }
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            apserv.rvectorsetlengthatleast(ref grad, wcount, _params);
+            alglib.smp.ae_shared_pool_first_recycled(network.gradbuf, ref sgrad);
+            while( sgrad!=null )
+            {
+                sgrad.f = 0.0;
+                for(i=0; i<=wcount-1; i++)
+                {
+                    sgrad.g[i] = 0.0;
+                }
+                alglib.smp.ae_shared_pool_next_recycled(network.gradbuf, ref sgrad);
+            }
+            mlpgradbatchx(network, xy, network.dummysxy, setsize, 0, idx, subset0, subset1, subsettype, network.buf, network.gradbuf, _params);
+            e = 0.0;
+            for(i=0; i<=wcount-1; i++)
+            {
+                grad[i] = 0.0;
+            }
+            alglib.smp.ae_shared_pool_first_recycled(network.gradbuf, ref sgrad);
+            while( sgrad!=null )
+            {
+                e = e+sgrad.f;
+                for(i=0; i<=wcount-1; i++)
+                {
+                    grad[i] = grad[i]+sgrad.g[i];
+                }
+                alglib.smp.ae_shared_pool_next_recycled(network.gradbuf, ref sgrad);
+            }
+        }
+
+
+        /*************************************************************************
+        Batch gradient calculation for a set of inputs/outputs  for  a  subset  of
+        dataset given by set of indexes.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network -   network initialized with one of the network creation funcs
+            XY      -   original dataset in sparse format; one sample = one row:
+                        * MATRIX MUST BE STORED IN CRS FORMAT
+                        * first NIn columns contain inputs,
+                        * for regression problem, next NOut columns store
+                          desired outputs.
+                        * for classification problem, next column (just one!)
+                          stores class number.
+            SetSize -   real size of XY, SetSize>=0;
+            Idx     -   subset of SubsetSize elements, array[SubsetSize]:
+                        * Idx[I] stores row index in the original dataset which is
+                          given by XY. Gradient is calculated with respect to rows
+                          whose indexes are stored in Idx[].
+                        * Idx[]  must store correct indexes; this function  throws
+                          an  exception  in  case  incorrect index (less than 0 or
+                          larger than rows(XY)) is given
+                        * Idx[]  may  store  indexes  in  any  order and even with
+                          repetitions.
+            SubsetSize- number of elements in Idx[] array:
+                        * positive value means that subset given by Idx[] is processed
+                        * zero value results in zero gradient
+                        * negative value means that full dataset is processed
+            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]
+
+        NOTE: when  SubsetSize<0 is used full dataset by call MLPGradBatchSparse
+              function.
+            
+          -- ALGLIB --
+             Copyright 26.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpgradbatchsparsesubset(multilayerperceptron network,
+            sparse.sparsematrix xy,
+            int setsize,
+            int[] idx,
+            int subsetsize,
+            ref double e,
+            ref double[] grad,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int npoints = 0;
+            int subset0 = 0;
+            int subset1 = 0;
+            int subsettype = 0;
+            smlpgrad sgrad = null;
+
+            e = 0;
+
+            alglib.ap.assert(setsize>=0, "MLPGradBatchSparseSubset: SetSize<0");
+            alglib.ap.assert(subsetsize<=alglib.ap.len(idx), "MLPGradBatchSparseSubset: SubsetSize>Length(Idx)");
+            alglib.ap.assert(sparse.sparseiscrs(xy, _params), "MLPGradBatchSparseSubset: sparse matrix XY must be in CRS format.");
+            npoints = setsize;
+            if( subsetsize<0 )
+            {
+                subset0 = 0;
+                subset1 = setsize;
+                subsettype = 0;
+            }
+            else
+            {
+                subset0 = 0;
+                subset1 = subsetsize;
+                subsettype = 1;
+                for(i=0; i<=subsetsize-1; i++)
+                {
+                    alglib.ap.assert(idx[i]>=0, "MLPGradBatchSparseSubset: incorrect index of XY row(Idx[I]<0)");
+                    alglib.ap.assert(idx[i]<=npoints-1, "MLPGradBatchSparseSubset: incorrect index of XY row(Idx[I]>Rows(XY)-1)");
+                }
+            }
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            apserv.rvectorsetlengthatleast(ref grad, wcount, _params);
+            alglib.smp.ae_shared_pool_first_recycled(network.gradbuf, ref sgrad);
+            while( sgrad!=null )
+            {
+                sgrad.f = 0.0;
+                for(i=0; i<=wcount-1; i++)
+                {
+                    sgrad.g[i] = 0.0;
+                }
+                alglib.smp.ae_shared_pool_next_recycled(network.gradbuf, ref sgrad);
+            }
+            mlpgradbatchx(network, network.dummydxy, xy, setsize, 1, idx, subset0, subset1, subsettype, network.buf, network.gradbuf, _params);
+            e = 0.0;
+            for(i=0; i<=wcount-1; i++)
+            {
+                grad[i] = 0.0;
+            }
+            alglib.smp.ae_shared_pool_first_recycled(network.gradbuf, ref sgrad);
+            while( sgrad!=null )
+            {
+                e = e+sgrad.f;
+                for(i=0; i<=wcount-1; i++)
+                {
+                    grad[i] = grad[i]+sgrad.g[i];
+                }
+                alglib.smp.ae_shared_pool_next_recycled(network.gradbuf, ref sgrad);
+            }
+        }
+
+
+        /*************************************************************************
+        Internal function which actually calculates batch gradient for a subset or
+        full dataset, which can be represented in different formats.
+
+        THIS FUNCTION IS NOT INTENDED TO BE USED BY ALGLIB USERS!
+
+          -- ALGLIB --
+             Copyright 26.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpgradbatchx(multilayerperceptron network,
+            double[,] densexy,
+            sparse.sparsematrix sparsexy,
+            int datasetsize,
+            int datasettype,
+            int[] idx,
+            int subset0,
+            int subset1,
+            int subsettype,
+            alglib.smp.shared_pool buf,
+            alglib.smp.shared_pool gradbuf,
+            alglib.xparams _params)
+        {
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int rowsize = 0;
+            int srcidx = 0;
+            int cstart = 0;
+            int csize = 0;
+            int j = 0;
+            double problemcost = 0;
+            hpccores.mlpbuffers buf2 = null;
+            int len0 = 0;
+            int len1 = 0;
+            hpccores.mlpbuffers pbuf = null;
+            smlpgrad sgrad = null;
+            int i_ = 0;
+
+            alglib.ap.assert(datasetsize>=0, "MLPGradBatchX: SetSize<0");
+            alglib.ap.assert(datasettype==0 || datasettype==1, "MLPGradBatchX: DatasetType is incorrect");
+            alglib.ap.assert(subsettype==0 || subsettype==1, "MLPGradBatchX: SubsetType is incorrect");
+            
+            //
+            // Determine network and dataset properties
+            //
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            if( mlpissoftmax(network, _params) )
+            {
+                rowsize = nin+1;
+            }
+            else
+            {
+                rowsize = nin+nout;
+            }
+            
+            //
+            // Split problem.
+            //
+            // Splitting problem allows us to reduce  effect  of  single-precision
+            // arithmetics (SSE-optimized version of MLPChunkedGradient uses single
+            // precision  internally, but converts them to  double precision after
+            // results are exported from HPC buffer to network). Small batches are
+            // calculated in single precision, results are  aggregated  in  double
+            // precision, and it allows us to avoid accumulation  of  errors  when
+            // we process very large batches (tens of thousands of items).
+            //
+            // NOTE: it is important to use real arithmetics for ProblemCost
+            //       because ProblemCost may be larger than MAXINT.
+            //
+            problemcost = subset1-subset0;
+            problemcost = problemcost*wcount*2;
+            if( (double)(problemcost)>=(double)(apserv.smpactivationlevel(_params)) && subset1-subset0>=2*microbatchsize )
+            {
+                if( _trypexec_mlpgradbatchx(network,densexy,sparsexy,datasetsize,datasettype,idx,subset0,subset1,subsettype,buf,gradbuf, _params) )
+                {
+                    return;
+                }
+            }
+            if( subset1-subset0>=2*microbatchsize && (double)(problemcost)>(double)(apserv.spawnlevel(_params)) )
+            {
+                apserv.splitlength(subset1-subset0, microbatchsize, ref len0, ref len1, _params);
+                mlpgradbatchx(network, densexy, sparsexy, datasetsize, datasettype, idx, subset0, subset0+len0, subsettype, buf, gradbuf, _params);
+                mlpgradbatchx(network, densexy, sparsexy, datasetsize, datasettype, idx, subset0+len0, subset1, subsettype, buf, gradbuf, _params);
+                return;
+            }
+            
+            //
+            // Chunked processing
+            //
+            alglib.smp.ae_shared_pool_retrieve(gradbuf, ref sgrad);
+            alglib.smp.ae_shared_pool_retrieve(buf, ref pbuf);
+            hpccores.hpcpreparechunkedgradient(network.weights, wcount, mlpntotal(network, _params), nin, nout, pbuf, _params);
+            cstart = subset0;
+            while( cstart<subset1 )
+            {
+                
+                //
+                // Determine size of current chunk and copy it to PBuf.XY
+                //
+                csize = Math.Min(subset1, cstart+pbuf.chunksize)-cstart;
+                for(j=0; j<=csize-1; j++)
+                {
+                    srcidx = -1;
+                    if( subsettype==0 )
+                    {
+                        srcidx = cstart+j;
+                    }
+                    if( subsettype==1 )
+                    {
+                        srcidx = idx[cstart+j];
+                    }
+                    alglib.ap.assert(srcidx>=0, "MLPGradBatchX: internal error");
+                    if( datasettype==0 )
+                    {
+                        for(i_=0; i_<=rowsize-1;i_++)
+                        {
+                            pbuf.xy[j,i_] = densexy[srcidx,i_];
+                        }
+                    }
+                    if( datasettype==1 )
+                    {
+                        sparse.sparsegetrow(sparsexy, srcidx, ref pbuf.xyrow, _params);
+                        for(i_=0; i_<=rowsize-1;i_++)
+                        {
+                            pbuf.xy[j,i_] = pbuf.xyrow[i_];
+                        }
+                    }
+                }
+                
+                //
+                // Process chunk and advance line pointer
+                //
+                mlpchunkedgradient(network, pbuf.xy, 0, csize, pbuf.batch4buf, pbuf.hpcbuf, ref sgrad.f, false, _params);
+                cstart = cstart+pbuf.chunksize;
+            }
+            hpccores.hpcfinalizechunkedgradient(pbuf, sgrad.g, _params);
+            alglib.smp.ae_shared_pool_recycle(buf, ref pbuf);
+            alglib.smp.ae_shared_pool_recycle(gradbuf, ref sgrad);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_mlpgradbatchx(multilayerperceptron network,
+            double[,] densexy,
+            sparse.sparsematrix sparsexy,
+            int datasetsize,
+            int datasettype,
+            int[] idx,
+            int subset0,
+            int subset1,
+            int subsettype,
+            alglib.smp.shared_pool buf,
+            alglib.smp.shared_pool gradbuf, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            hpccores.mlpbuffers pbuf = null;
+
+            e = 0;
+
+            
+            //
+            // Alloc
+            //
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            alglib.smp.ae_shared_pool_retrieve(network.buf, ref pbuf);
+            hpccores.hpcpreparechunkedgradient(network.weights, wcount, mlpntotal(network, _params), nin, nout, pbuf, _params);
+            apserv.rvectorsetlengthatleast(ref grad, wcount, _params);
+            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+pbuf.chunksize)-i, pbuf.batch4buf, pbuf.hpcbuf, ref e, true, _params);
+                i = i+pbuf.chunksize;
+            }
+            hpccores.hpcfinalizechunkedgradient(pbuf, grad, _params);
+            alglib.smp.ae_shared_pool_recycle(network.buf, ref pbuf);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            e = 0;
+
+            mlphessianbatchinternal(network, xy, ssize, true, ref e, ref grad, ref h, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            e = 0;
+
+            mlphessianbatchinternal(network, xy, ssize, false, ref e, ref grad, ref h, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                    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;
+                    apserv.touchint(ref n2, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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, _params);
+                    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, _params);
+                s.alloc_entry();
+                s.alloc_entry();
+            }
+            for(j=0; j<=nout-1; j++)
+            {
+                mlpgetoutputscaling(network, j, ref v0, ref v1, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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(_params));
+            s.serialize_int(mlpfirstversion);
+            s.serialize_bool(mlpissoftmax(network, _params));
+            apserv.serializeintegerarray(s, network.hllayersizes, -1, _params);
+            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, _params);
+                    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, _params));
+                    }
+                }
+            }
+            for(j=0; j<=nin-1; j++)
+            {
+                mlpgetinputscaling(network, j, ref v0, ref v1, _params);
+                s.serialize_double(v0);
+                s.serialize_double(v1);
+            }
+            for(j=0; j<=nout-1; j++)
+            {
+                mlpgetoutputscaling(network, j, ref v0, ref v1, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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(_params), "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, _params);
+            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, _params);
+                }
+                else
+                {
+                    mlpcreate0(layersizes[0], layersizes[1], network, _params);
+                }
+            }
+            if( alglib.ap.len(layersizes)==3 )
+            {
+                if( issoftmax )
+                {
+                    mlpcreatec1(layersizes[0], layersizes[1], layersizes[2], network, _params);
+                }
+                else
+                {
+                    mlpcreate1(layersizes[0], layersizes[1], layersizes[2], network, _params);
+                }
+            }
+            if( alglib.ap.len(layersizes)==4 )
+            {
+                if( issoftmax )
+                {
+                    mlpcreatec2(layersizes[0], layersizes[1], layersizes[2], layersizes[3], network, _params);
+                }
+                else
+                {
+                    mlpcreate2(layersizes[0], layersizes[1], layersizes[2], layersizes[3], network, _params);
+                }
+            }
+            
+            //
+            // 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, _params);
+                    for(k=0; k<=layersizes[i-1]-1; k++)
+                    {
+                        v0 = s.unserialize_double();
+                        mlpsetweight(network, i-1, k, i, j, v0, _params);
+                    }
+                }
+            }
+            
+            //
+            // Load standartizator
+            //
+            for(j=0; j<=nin-1; j++)
+            {
+                v0 = s.unserialize_double();
+                v1 = s.unserialize_double();
+                mlpsetinputscaling(network, j, v0, v1, _params);
+            }
+            for(j=0; j<=nout-1; j++)
+            {
+                v0 = s.unserialize_double();
+                v1 = s.unserialize_double();
+                mlpsetoutputscaling(network, j, v0, v1, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Calculation of all types of errors on subset of dataset.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network -   network initialized with one of the network creation funcs
+            XY      -   original dataset; one sample = one row;
+                        first NIn columns contain inputs,
+                        next NOut columns - desired outputs.
+            SetSize -   real size of XY, SetSize>=0;
+            Subset  -   subset of SubsetSize elements, array[SubsetSize];
+            SubsetSize- number of elements in Subset[] array:
+                        * if SubsetSize>0, rows of XY with indices Subset[0]...
+                          ...Subset[SubsetSize-1] are processed
+                        * if SubsetSize=0, zeros are returned
+                        * if SubsetSize<0, entire dataset is  processed;  Subset[]
+                          array is ignored in this case.
+
+        OUTPUT PARAMETERS:
+            Rep     -   it contains all type of errors.
+
+          -- ALGLIB --
+             Copyright 04.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpallerrorssubset(multilayerperceptron network,
+            double[,] xy,
+            int setsize,
+            int[] subset,
+            int subsetsize,
+            modelerrors rep,
+            alglib.xparams _params)
+        {
+            int idx0 = 0;
+            int idx1 = 0;
+            int idxtype = 0;
+
+            alglib.ap.assert(alglib.ap.rows(xy)>=setsize, "MLPAllErrorsSubset: XY has less than SetSize rows");
+            if( setsize>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+1, "MLPAllErrorsSubset: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPAllErrorsSubset: XY has less than NIn+NOut columns");
+                }
+            }
+            if( subsetsize>=0 )
+            {
+                idx0 = 0;
+                idx1 = subsetsize;
+                idxtype = 1;
+            }
+            else
+            {
+                idx0 = 0;
+                idx1 = setsize;
+                idxtype = 0;
+            }
+            mlpallerrorsx(network, xy, network.dummysxy, setsize, 0, subset, idx0, idx1, idxtype, network.buf, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Calculation of all types of errors on subset of dataset.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network -   network initialized with one of the network creation funcs
+            XY      -   original dataset given by sparse matrix;
+                        one sample = one row;
+                        first NIn columns contain inputs,
+                        next NOut columns - desired outputs.
+            SetSize -   real size of XY, SetSize>=0;
+            Subset  -   subset of SubsetSize elements, array[SubsetSize];
+            SubsetSize- number of elements in Subset[] array:
+                        * if SubsetSize>0, rows of XY with indices Subset[0]...
+                          ...Subset[SubsetSize-1] are processed
+                        * if SubsetSize=0, zeros are returned
+                        * if SubsetSize<0, entire dataset is  processed;  Subset[]
+                          array is ignored in this case.
+
+        OUTPUT PARAMETERS:
+            Rep     -   it contains all type of errors.
+
+
+          -- ALGLIB --
+             Copyright 04.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpallerrorssparsesubset(multilayerperceptron network,
+            sparse.sparsematrix xy,
+            int setsize,
+            int[] subset,
+            int subsetsize,
+            modelerrors rep,
+            alglib.xparams _params)
+        {
+            int idx0 = 0;
+            int idx1 = 0;
+            int idxtype = 0;
+
+            alglib.ap.assert(sparse.sparseiscrs(xy, _params), "MLPAllErrorsSparseSubset: XY is not in CRS format.");
+            alglib.ap.assert(sparse.sparsegetnrows(xy, _params)>=setsize, "MLPAllErrorsSparseSubset: XY has less than SetSize rows");
+            if( setsize>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+1, "MLPAllErrorsSparseSubset: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPAllErrorsSparseSubset: XY has less than NIn+NOut columns");
+                }
+            }
+            if( subsetsize>=0 )
+            {
+                idx0 = 0;
+                idx1 = subsetsize;
+                idxtype = 1;
+            }
+            else
+            {
+                idx0 = 0;
+                idx1 = setsize;
+                idxtype = 0;
+            }
+            mlpallerrorsx(network, network.dummydxy, xy, setsize, 1, subset, idx0, idx1, idxtype, network.buf, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Error of the neural network on subset of dataset.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network   -     neural network;
+            XY        -     training  set,  see  below  for  information  on   the
+                            training set format;
+            SetSize   -     real size of XY, SetSize>=0;
+            Subset    -     subset of SubsetSize elements, array[SubsetSize];
+            SubsetSize-     number of elements in Subset[] array:
+                            * if SubsetSize>0, rows of XY with indices Subset[0]...
+                              ...Subset[SubsetSize-1] are processed
+                            * if SubsetSize=0, zeros are returned
+                            * if SubsetSize<0, entire dataset is  processed;  Subset[]
+                              array is ignored in this case.
+
+        RESULT:
+            sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2)
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+
+          -- ALGLIB --
+             Copyright 04.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlperrorsubset(multilayerperceptron network,
+            double[,] xy,
+            int setsize,
+            int[] subset,
+            int subsetsize,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int idx0 = 0;
+            int idx1 = 0;
+            int idxtype = 0;
+
+            alglib.ap.assert(alglib.ap.rows(xy)>=setsize, "MLPErrorSubset: XY has less than SetSize rows");
+            if( setsize>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+1, "MLPErrorSubset: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(alglib.ap.cols(xy)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPErrorSubset: XY has less than NIn+NOut columns");
+                }
+            }
+            if( subsetsize>=0 )
+            {
+                idx0 = 0;
+                idx1 = subsetsize;
+                idxtype = 1;
+            }
+            else
+            {
+                idx0 = 0;
+                idx1 = setsize;
+                idxtype = 0;
+            }
+            mlpallerrorsx(network, xy, network.dummysxy, setsize, 0, subset, idx0, idx1, idxtype, network.buf, network.err, _params);
+            result = math.sqr(network.err.rmserror)*(idx1-idx0)*mlpgetoutputscount(network, _params)/2;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Error of the neural network on subset of sparse dataset.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            Network   -     neural network;
+            XY        -     training  set,  see  below  for  information  on   the
+                            training set format. This function checks  correctness
+                            of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                            correct) and throws exception when  incorrect  dataset
+                            is passed.  Sparse  matrix  must  use  CRS  format for
+                            storage.
+            SetSize   -     real size of XY, SetSize>=0;
+                            it is used when SubsetSize<0;
+            Subset    -     subset of SubsetSize elements, array[SubsetSize];
+            SubsetSize-     number of elements in Subset[] array:
+                            * if SubsetSize>0, rows of XY with indices Subset[0]...
+                              ...Subset[SubsetSize-1] are processed
+                            * if SubsetSize=0, zeros are returned
+                            * if SubsetSize<0, entire dataset is  processed;  Subset[]
+                              array is ignored in this case.
+
+        RESULT:
+            sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2)
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        dataset format is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+
+          -- ALGLIB --
+             Copyright 04.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mlperrorsparsesubset(multilayerperceptron network,
+            sparse.sparsematrix xy,
+            int setsize,
+            int[] subset,
+            int subsetsize,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int idx0 = 0;
+            int idx1 = 0;
+            int idxtype = 0;
+
+            alglib.ap.assert(sparse.sparseiscrs(xy, _params), "MLPErrorSparseSubset: XY is not in CRS format.");
+            alglib.ap.assert(sparse.sparsegetnrows(xy, _params)>=setsize, "MLPErrorSparseSubset: XY has less than SetSize rows");
+            if( setsize>0 )
+            {
+                if( mlpissoftmax(network, _params) )
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+1, "MLPErrorSparseSubset: XY has less than NIn+1 columns");
+                }
+                else
+                {
+                    alglib.ap.assert(sparse.sparsegetncols(xy, _params)>=mlpgetinputscount(network, _params)+mlpgetoutputscount(network, _params), "MLPErrorSparseSubset: XY has less than NIn+NOut columns");
+                }
+            }
+            if( subsetsize>=0 )
+            {
+                idx0 = 0;
+                idx1 = subsetsize;
+                idxtype = 1;
+            }
+            else
+            {
+                idx0 = 0;
+                idx1 = setsize;
+                idxtype = 0;
+            }
+            mlpallerrorsx(network, network.dummydxy, xy, setsize, 1, subset, idx0, idx1, idxtype, network.buf, network.err, _params);
+            result = math.sqr(network.err.rmserror)*(idx1-idx0)*mlpgetoutputscount(network, _params)/2;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Calculation of all types of errors at once for a subset or  full  dataset,
+        which can be represented in different formats.
+
+        THIS INTERNAL FUNCTION IS NOT INTENDED TO BE USED BY ALGLIB USERS!
+
+          -- ALGLIB --
+             Copyright 26.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpallerrorsx(multilayerperceptron network,
+            double[,] densexy,
+            sparse.sparsematrix sparsexy,
+            int datasetsize,
+            int datasettype,
+            int[] idx,
+            int subset0,
+            int subset1,
+            int subsettype,
+            alglib.smp.shared_pool buf,
+            modelerrors rep,
+            alglib.xparams _params)
+        {
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int rowsize = 0;
+            bool iscls = new bool();
+            int srcidx = 0;
+            int cstart = 0;
+            int csize = 0;
+            int j = 0;
+            hpccores.mlpbuffers pbuf = null;
+            int len0 = 0;
+            int len1 = 0;
+            modelerrors rep0 = new modelerrors();
+            modelerrors rep1 = new modelerrors();
+            double problemcost = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert(datasetsize>=0, "MLPAllErrorsX: SetSize<0");
+            alglib.ap.assert(datasettype==0 || datasettype==1, "MLPAllErrorsX: DatasetType is incorrect");
+            alglib.ap.assert(subsettype==0 || subsettype==1, "MLPAllErrorsX: SubsetType is incorrect");
+            
+            //
+            // Determine network properties
+            //
+            mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            iscls = mlpissoftmax(network, _params);
+            
+            //
+            // Split problem.
+            //
+            // Splitting problem allows us to reduce  effect  of  single-precision
+            // arithmetics (SSE-optimized version of MLPChunkedProcess uses single
+            // precision  internally, but converts them to  double precision after
+            // results are exported from HPC buffer to network). Small batches are
+            // calculated in single precision, results are  aggregated  in  double
+            // precision, and it allows us to avoid accumulation  of  errors  when
+            // we process very large batches (tens of thousands of items).
+            //
+            // NOTE: it is important to use real arithmetics for ProblemCost
+            //       because ProblemCost may be larger than MAXINT.
+            //
+            problemcost = subset1-subset0;
+            problemcost = problemcost*wcount*2;
+            if( (double)(problemcost)>=(double)(apserv.smpactivationlevel(_params)) && subset1-subset0>=2*microbatchsize )
+            {
+                if( _trypexec_mlpallerrorsx(network,densexy,sparsexy,datasetsize,datasettype,idx,subset0,subset1,subsettype,buf,rep, _params) )
+                {
+                    return;
+                }
+            }
+            if( subset1-subset0>=2*microbatchsize && (double)(problemcost)>(double)(apserv.spawnlevel(_params)) )
+            {
+                apserv.splitlength(subset1-subset0, microbatchsize, ref len0, ref len1, _params);
+                mlpallerrorsx(network, densexy, sparsexy, datasetsize, datasettype, idx, subset0, subset0+len0, subsettype, buf, rep0, _params);
+                mlpallerrorsx(network, densexy, sparsexy, datasetsize, datasettype, idx, subset0+len0, subset1, subsettype, buf, rep1, _params);
+                rep.relclserror = (len0*rep0.relclserror+len1*rep1.relclserror)/(len0+len1);
+                rep.avgce = (len0*rep0.avgce+len1*rep1.avgce)/(len0+len1);
+                rep.rmserror = Math.Sqrt((len0*math.sqr(rep0.rmserror)+len1*math.sqr(rep1.rmserror))/(len0+len1));
+                rep.avgerror = (len0*rep0.avgerror+len1*rep1.avgerror)/(len0+len1);
+                rep.avgrelerror = (len0*rep0.avgrelerror+len1*rep1.avgrelerror)/(len0+len1);
+                return;
+            }
+            
+            //
+            // Retrieve and prepare
+            //
+            alglib.smp.ae_shared_pool_retrieve(buf, ref pbuf);
+            if( iscls )
+            {
+                rowsize = nin+1;
+                bdss.dserrallocate(nout, ref pbuf.tmp0, _params);
+            }
+            else
+            {
+                rowsize = nin+nout;
+                bdss.dserrallocate(-nout, ref pbuf.tmp0, _params);
+            }
+            
+            //
+            // Processing
+            //
+            hpccores.hpcpreparechunkedgradient(network.weights, wcount, mlpntotal(network, _params), nin, nout, pbuf, _params);
+            cstart = subset0;
+            while( cstart<subset1 )
+            {
+                
+                //
+                // Determine size of current chunk and copy it to PBuf.XY
+                //
+                csize = Math.Min(subset1, cstart+pbuf.chunksize)-cstart;
+                for(j=0; j<=csize-1; j++)
+                {
+                    srcidx = -1;
+                    if( subsettype==0 )
+                    {
+                        srcidx = cstart+j;
+                    }
+                    if( subsettype==1 )
+                    {
+                        srcidx = idx[cstart+j];
+                    }
+                    alglib.ap.assert(srcidx>=0, "MLPAllErrorsX: internal error");
+                    if( datasettype==0 )
+                    {
+                        for(i_=0; i_<=rowsize-1;i_++)
+                        {
+                            pbuf.xy[j,i_] = densexy[srcidx,i_];
+                        }
+                    }
+                    if( datasettype==1 )
+                    {
+                        sparse.sparsegetrow(sparsexy, srcidx, ref pbuf.xyrow, _params);
+                        for(i_=0; i_<=rowsize-1;i_++)
+                        {
+                            pbuf.xy[j,i_] = pbuf.xyrow[i_];
+                        }
+                    }
+                }
+                
+                //
+                // Unpack XY and process (temporary code, to be replaced by chunked processing)
+                //
+                for(j=0; j<=csize-1; j++)
+                {
+                    for(i_=0; i_<=rowsize-1;i_++)
+                    {
+                        pbuf.xy2[j,i_] = pbuf.xy[j,i_];
+                    }
+                }
+                mlpchunkedprocess(network, pbuf.xy2, 0, csize, pbuf.batch4buf, pbuf.hpcbuf, _params);
+                for(j=0; j<=csize-1; j++)
+                {
+                    for(i_=0; i_<=nin-1;i_++)
+                    {
+                        pbuf.x[i_] = pbuf.xy2[j,i_];
+                    }
+                    i1_ = (nin) - (0);
+                    for(i_=0; i_<=nout-1;i_++)
+                    {
+                        pbuf.y[i_] = pbuf.xy2[j,i_+i1_];
+                    }
+                    if( iscls )
+                    {
+                        pbuf.desiredy[0] = pbuf.xy[j,nin];
+                    }
+                    else
+                    {
+                        i1_ = (nin) - (0);
+                        for(i_=0; i_<=nout-1;i_++)
+                        {
+                            pbuf.desiredy[i_] = pbuf.xy[j,i_+i1_];
+                        }
+                    }
+                    bdss.dserraccumulate(ref pbuf.tmp0, pbuf.y, pbuf.desiredy, _params);
+                }
+                
+                //
+                // Process chunk and advance line pointer
+                //
+                cstart = cstart+pbuf.chunksize;
+            }
+            bdss.dserrfinish(ref pbuf.tmp0, _params);
+            rep.relclserror = pbuf.tmp0[0];
+            rep.avgce = pbuf.tmp0[1]/Math.Log(2);
+            rep.rmserror = pbuf.tmp0[2];
+            rep.avgerror = pbuf.tmp0[3];
+            rep.avgrelerror = pbuf.tmp0[4];
+            
+            //
+            // Recycle
+            //
+            alglib.smp.ae_shared_pool_recycle(buf, ref pbuf);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_mlpallerrorsx(multilayerperceptron network,
+            double[,] densexy,
+            sparse.sparsematrix sparsexy,
+            int datasetsize,
+            int datasettype,
+            int[] idx,
+            int subset0,
+            int subset1,
+            int subsettype,
+            alglib.smp.shared_pool buf,
+            modelerrors rep, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                hladdoutputlayer(network, ref idxconn, ref idxneuro, ref idxstruct, ref idxweights, 1, nin, nout, iscls, islinearout, _params);
+                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, _params);
+                hladdhiddenlayer(network, ref idxconn, ref idxneuro, ref idxstruct, ref idxweights, 1, nin, nhid1, _params);
+                hladdoutputlayer(network, ref idxconn, ref idxneuro, ref idxstruct, ref idxweights, 2, nhid1, nout, iscls, islinearout, _params);
+                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, _params);
+            hladdhiddenlayer(network, ref idxconn, ref idxneuro, ref idxstruct, ref idxweights, 1, nin, nhid1, _params);
+            hladdhiddenlayer(network, ref idxconn, ref idxneuro, ref idxstruct, ref idxweights, 2, nhid1, nhid2, _params);
+            hladdoutputlayer(network, ref idxconn, ref idxneuro, ref idxstruct, ref idxweights, 3, nhid2, nout, iscls, islinearout, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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];
+            hpccores.mlpbuffers buf = new hpccores.mlpbuffers();
+            smlpgrad sgrad = new smlpgrad();
+
+            
+            //
+            // 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.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:
+            // * first, fill by dummy values to avoid spurious reports by Valgrind
+            // * then fill global info header
+            //
+            for(i=0; i<=ssize-1; i++)
+            {
+                network.structinfo[i] = -999999;
+            }
+            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<=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;
+                }
+            }
+            mlprandomize(network, _params);
+            
+            //
+            // Seed buffers
+            //
+            alglib.smp.ae_shared_pool_set_seed(network.buf, buf);
+            sgrad.g = new double[wcount];
+            sgrad.f = 0.0;
+            for(i=0; i<=wcount-1; i++)
+            {
+                sgrad.g[i] = 0.0;
+            }
+            alglib.smp.ae_shared_pool_set_seed(network.gradbuf, sgrad);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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, _params) )
+                {
+                    
+                    //
+                    // 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, _params);
+                }
+                else
+                {
+                    mlpgrad(network, x, desiredy, ref et, ref gt, _params);
+                }
+                
+                //
+                // 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, _params);
+                        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,
+            alglib.xparams _params)
+        {
+            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;
+                }
+            }
+        }
+
+
+        private static void mlpchunkedgradient(multilayerperceptron network,
+            double[,] xy,
+            int cstart,
+            int csize,
+            double[] batch4buf,
+            double[] hpcbuf,
+            ref double e,
+            bool naturalerrorfunc,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int kl = 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 vv = 0;
+            double s = 0;
+            double fown = 0;
+            double deown = 0;
+            bool bflag = new bool();
+            int istart = 0;
+            int entrysize = 0;
+            int dfoffs = 0;
+            int derroroffs = 0;
+            int entryoffs = 0;
+            int neuronidx = 0;
+            int srcentryoffs = 0;
+            int srcneuronidx = 0;
+            int srcweightidx = 0;
+            int neurontype = 0;
+            int nweights = 0;
+            int offs0 = 0;
+            int offs1 = 0;
+            int offs2 = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double v3 = 0;
+            double s0 = 0;
+            double s1 = 0;
+            double s2 = 0;
+            double s3 = 0;
+            int chunksize = 0;
+
+            chunksize = 4;
+            alglib.ap.assert(csize<=chunksize, "MLPChunkedGradient: internal error (CSize>ChunkSize)");
+            
+            //
+            // Try to use HPC core, if possible
+            //
+            if( hpccores.hpcchunkedgradient(network.weights, network.structinfo, network.columnmeans, network.columnsigmas, xy, cstart, csize, batch4buf, hpcbuf, ref e, naturalerrorfunc, _params) )
+            {
+                return;
+            }
+            
+            //
+            // Read network geometry, prepare data
+            //
+            nin = network.structinfo[1];
+            nout = network.structinfo[2];
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            entrysize = 12;
+            dfoffs = 4;
+            derroroffs = 8;
+            
+            //
+            // Fill Batch4Buf by zeros.
+            //
+            // THIS STAGE IS VERY IMPORTANT!
+            //
+            // We fill all components of entry - neuron values, dF/dNET, dError/dF.
+            // It allows us to easily handle  situations  when  CSize<ChunkSize  by
+            // simply  working  with  ALL  components  of  Batch4Buf,  without ever
+            // looking at CSize. The idea is that dError/dF for  absent  components
+            // will be initialized by zeros - and won't be  rewritten  by  non-zero
+            // values during backpropagation.
+            //
+            for(i=0; i<=entrysize*ntotal-1; i++)
+            {
+                batch4buf[i] = 0;
+            }
+            
+            //
+            // Forward pass:
+            // 1. Load data into Batch4Buf. If CSize<ChunkSize, data are padded by zeros.
+            // 2. Perform forward pass through network
+            //
+            for(i=0; i<=nin-1; i++)
+            {
+                entryoffs = entrysize*i;
+                for(j=0; j<=csize-1; j++)
+                {
+                    if( (double)(network.columnsigmas[i])!=(double)(0) )
+                    {
+                        batch4buf[entryoffs+j] = (xy[cstart+j,i]-network.columnmeans[i])/network.columnsigmas[i];
+                    }
+                    else
+                    {
+                        batch4buf[entryoffs+j] = xy[cstart+j,i]-network.columnmeans[i];
+                    }
+                }
+            }
+            for(neuronidx=0; neuronidx<=ntotal-1; neuronidx++)
+            {
+                entryoffs = entrysize*neuronidx;
+                offs = istart+neuronidx*nfieldwidth;
+                neurontype = network.structinfo[offs+0];
+                if( neurontype>0 || neurontype==-5 )
+                {
+                    
+                    //
+                    // "activation function" neuron, which takes value of neuron SrcNeuronIdx
+                    // and applies activation function to it.
+                    //
+                    // This neuron has no weights and no tunable parameters.
+                    //
+                    srcneuronidx = network.structinfo[offs+2];
+                    srcentryoffs = entrysize*srcneuronidx;
+                    mlpactivationfunction(batch4buf[srcentryoffs+0], neurontype, ref f, ref df, ref d2f, _params);
+                    batch4buf[entryoffs+0] = f;
+                    batch4buf[entryoffs+0+dfoffs] = df;
+                    mlpactivationfunction(batch4buf[srcentryoffs+1], neurontype, ref f, ref df, ref d2f, _params);
+                    batch4buf[entryoffs+1] = f;
+                    batch4buf[entryoffs+1+dfoffs] = df;
+                    mlpactivationfunction(batch4buf[srcentryoffs+2], neurontype, ref f, ref df, ref d2f, _params);
+                    batch4buf[entryoffs+2] = f;
+                    batch4buf[entryoffs+2+dfoffs] = df;
+                    mlpactivationfunction(batch4buf[srcentryoffs+3], neurontype, ref f, ref df, ref d2f, _params);
+                    batch4buf[entryoffs+3] = f;
+                    batch4buf[entryoffs+3+dfoffs] = df;
+                    continue;
+                }
+                if( neurontype==0 )
+                {
+                    
+                    //
+                    // "adaptive summator" neuron, whose output is a weighted sum of inputs.
+                    // It has weights, but has no activation function.
+                    //
+                    nweights = network.structinfo[offs+1];
+                    srcneuronidx = network.structinfo[offs+2];
+                    srcentryoffs = entrysize*srcneuronidx;
+                    srcweightidx = network.structinfo[offs+3];
+                    v0 = 0;
+                    v1 = 0;
+                    v2 = 0;
+                    v3 = 0;
+                    for(j=0; j<=nweights-1; j++)
+                    {
+                        v = network.weights[srcweightidx];
+                        srcweightidx = srcweightidx+1;
+                        v0 = v0+v*batch4buf[srcentryoffs+0];
+                        v1 = v1+v*batch4buf[srcentryoffs+1];
+                        v2 = v2+v*batch4buf[srcentryoffs+2];
+                        v3 = v3+v*batch4buf[srcentryoffs+3];
+                        srcentryoffs = srcentryoffs+entrysize;
+                    }
+                    batch4buf[entryoffs+0] = v0;
+                    batch4buf[entryoffs+1] = v1;
+                    batch4buf[entryoffs+2] = v2;
+                    batch4buf[entryoffs+3] = v3;
+                    batch4buf[entryoffs+0+dfoffs] = 1;
+                    batch4buf[entryoffs+1+dfoffs] = 1;
+                    batch4buf[entryoffs+2+dfoffs] = 1;
+                    batch4buf[entryoffs+3+dfoffs] = 1;
+                    continue;
+                }
+                if( neurontype<0 )
+                {
+                    bflag = false;
+                    if( neurontype==-2 )
+                    {
+                        
+                        //
+                        // Input neuron, left unchanged
+                        //
+                        bflag = true;
+                    }
+                    if( neurontype==-3 )
+                    {
+                        
+                        //
+                        // "-1" neuron
+                        //
+                        batch4buf[entryoffs+0] = -1;
+                        batch4buf[entryoffs+1] = -1;
+                        batch4buf[entryoffs+2] = -1;
+                        batch4buf[entryoffs+3] = -1;
+                        batch4buf[entryoffs+0+dfoffs] = 0;
+                        batch4buf[entryoffs+1+dfoffs] = 0;
+                        batch4buf[entryoffs+2+dfoffs] = 0;
+                        batch4buf[entryoffs+3+dfoffs] = 0;
+                        bflag = true;
+                    }
+                    if( neurontype==-4 )
+                    {
+                        
+                        //
+                        // "0" neuron
+                        //
+                        batch4buf[entryoffs+0] = 0;
+                        batch4buf[entryoffs+1] = 0;
+                        batch4buf[entryoffs+2] = 0;
+                        batch4buf[entryoffs+3] = 0;
+                        batch4buf[entryoffs+0+dfoffs] = 0;
+                        batch4buf[entryoffs+1+dfoffs] = 0;
+                        batch4buf[entryoffs+2+dfoffs] = 0;
+                        batch4buf[entryoffs+3+dfoffs] = 0;
+                        bflag = true;
+                    }
+                    alglib.ap.assert(bflag, "MLPChunkedGradient: internal error - unknown neuron type!");
+                    continue;
+                }
+            }
+            
+            //
+            // Intermediate phase between forward and backward passes.
+            //
+            // For regression networks:
+            // * forward pass is completely done (no additional post-processing is
+            //   needed).
+            // * before starting backward pass, we have to  calculate  dError/dOut
+            //   for output neurons. We also update error at this phase.
+            //
+            // For classification networks:
+            // * in addition to forward pass we  apply  SOFTMAX  normalization  to
+            //   output neurons.
+            // * after applying normalization, we have to  calculate  dError/dOut,
+            //   which is calculated in two steps:
+            //   * first, we calculate derivative of error with respect to SOFTMAX
+            //     normalized outputs (normalized dError)
+            //   * then,  we calculate derivative of error with respect to  values
+            //     of outputs BEFORE normalization was applied to them
+            //
+            alglib.ap.assert(network.structinfo[6]==0 || network.structinfo[6]==1, "MLPChunkedGradient: unknown normalization type!");
+            if( network.structinfo[6]==1 )
+            {
+                
+                //
+                // SOFTMAX-normalized network.
+                //
+                // First,  calculate (V0,V1,V2,V3)  -  component-wise  maximum
+                // of output neurons. This vector of maximum  values  will  be
+                // used for normalization  of  outputs  prior  to  calculating
+                // exponentials.
+                //
+                // NOTE: the only purpose of this stage is to prevent overflow
+                //       during calculation of exponentials.  With  this stage
+                //       we  make  sure  that  all exponentials are calculated
+                //       with non-positive argument. If you load (0,0,0,0)  to
+                //       (V0,V1,V2,V3), your program will continue  working  -
+                //       although with less robustness.
+                //
+                entryoffs = entrysize*(ntotal-nout);
+                v0 = batch4buf[entryoffs+0];
+                v1 = batch4buf[entryoffs+1];
+                v2 = batch4buf[entryoffs+2];
+                v3 = batch4buf[entryoffs+3];
+                entryoffs = entryoffs+entrysize;
+                for(i=1; i<=nout-1; i++)
+                {
+                    v = batch4buf[entryoffs+0];
+                    if( v>v0 )
+                    {
+                        v0 = v;
+                    }
+                    v = batch4buf[entryoffs+1];
+                    if( v>v1 )
+                    {
+                        v1 = v;
+                    }
+                    v = batch4buf[entryoffs+2];
+                    if( v>v2 )
+                    {
+                        v2 = v;
+                    }
+                    v = batch4buf[entryoffs+3];
+                    if( v>v3 )
+                    {
+                        v3 = v;
+                    }
+                    entryoffs = entryoffs+entrysize;
+                }
+                
+                //
+                // Then,  calculate exponentials and place them to part of the
+                // array which  is  located  past  the  last  entry.  We  also
+                // calculate sum of exponentials which will be stored past the
+                // exponentials.
+                //
+                entryoffs = entrysize*(ntotal-nout);
+                offs0 = entrysize*ntotal;
+                s0 = 0;
+                s1 = 0;
+                s2 = 0;
+                s3 = 0;
+                for(i=0; i<=nout-1; i++)
+                {
+                    v = Math.Exp(batch4buf[entryoffs+0]-v0);
+                    s0 = s0+v;
+                    batch4buf[offs0+0] = v;
+                    v = Math.Exp(batch4buf[entryoffs+1]-v1);
+                    s1 = s1+v;
+                    batch4buf[offs0+1] = v;
+                    v = Math.Exp(batch4buf[entryoffs+2]-v2);
+                    s2 = s2+v;
+                    batch4buf[offs0+2] = v;
+                    v = Math.Exp(batch4buf[entryoffs+3]-v3);
+                    s3 = s3+v;
+                    batch4buf[offs0+3] = v;
+                    entryoffs = entryoffs+entrysize;
+                    offs0 = offs0+chunksize;
+                }
+                offs0 = entrysize*ntotal+2*nout*chunksize;
+                batch4buf[offs0+0] = s0;
+                batch4buf[offs0+1] = s1;
+                batch4buf[offs0+2] = s2;
+                batch4buf[offs0+3] = s3;
+                
+                //
+                // Now we have:
+                // * Batch4Buf[0...EntrySize*NTotal-1] stores:
+                //   * NTotal*ChunkSize neuron output values (SOFTMAX normalization
+                //     was not applied to these values),
+                //   * NTotal*ChunkSize values of dF/dNET (derivative of neuron
+                //     output with respect to its input)
+                //   * NTotal*ChunkSize zeros in the elements which correspond to
+                //     dError/dOut (derivative of error with respect to neuron output).
+                // * Batch4Buf[EntrySize*NTotal...EntrySize*NTotal+ChunkSize*NOut-1] -
+                //   stores exponentials of last NOut neurons.
+                // * Batch4Buf[EntrySize*NTotal+ChunkSize*NOut-1...EntrySize*NTotal+ChunkSize*2*NOut-1]
+                //   - can be used for temporary calculations
+                // * Batch4Buf[EntrySize*NTotal+ChunkSize*2*NOut...EntrySize*NTotal+ChunkSize*2*NOut+ChunkSize-1]
+                //   - stores sum-of-exponentials
+                //
+                // Block below calculates derivatives of error function with respect 
+                // to non-SOFTMAX-normalized output values of last NOut neurons.
+                //
+                // It is quite complicated; we do not describe algebra behind it,
+                // but if you want you may check it yourself :)
+                //
+                if( naturalerrorfunc )
+                {
+                    
+                    //
+                    // Calculate  derivative  of  error  with respect to values of
+                    // output  neurons  PRIOR TO SOFTMAX NORMALIZATION. Because we
+                    // use natural error function (cross-entropy), we  can  do  so
+                    // very easy.
+                    //
+                    offs0 = entrysize*ntotal+2*nout*chunksize;
+                    for(k=0; k<=csize-1; k++)
+                    {
+                        s = batch4buf[offs0+k];
+                        kl = (int)Math.Round(xy[cstart+k,nin]);
+                        offs1 = (ntotal-nout)*entrysize+derroroffs+k;
+                        offs2 = entrysize*ntotal+k;
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            if( i==kl )
+                            {
+                                v = 1;
+                            }
+                            else
+                            {
+                                v = 0;
+                            }
+                            vv = batch4buf[offs2];
+                            batch4buf[offs1] = vv/s-v;
+                            e = e+safecrossentropy(v, vv/s, _params);
+                            offs1 = offs1+entrysize;
+                            offs2 = offs2+chunksize;
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // SOFTMAX normalization makes things very difficult.
+                    // Sorry, we do not dare to describe this esoteric math
+                    // in details.
+                    //
+                    offs0 = entrysize*ntotal+chunksize*2*nout;
+                    for(k=0; k<=csize-1; k++)
+                    {
+                        s = batch4buf[offs0+k];
+                        kl = (int)Math.Round(xy[cstart+k,nin]);
+                        vv = 0;
+                        offs1 = entrysize*ntotal+k;
+                        offs2 = entrysize*ntotal+nout*chunksize+k;
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            fown = batch4buf[offs1];
+                            if( i==kl )
+                            {
+                                deown = fown/s-1;
+                            }
+                            else
+                            {
+                                deown = fown/s;
+                            }
+                            batch4buf[offs2] = deown;
+                            vv = vv+deown*fown;
+                            e = e+deown*deown/2;
+                            offs1 = offs1+chunksize;
+                            offs2 = offs2+chunksize;
+                        }
+                        offs1 = entrysize*ntotal+k;
+                        offs2 = entrysize*ntotal+nout*chunksize+k;
+                        for(i=0; i<=nout-1; i++)
+                        {
+                            fown = batch4buf[offs1];
+                            deown = batch4buf[offs2];
+                            batch4buf[(ntotal-nout+i)*entrysize+derroroffs+k] = (-vv+deown*fown+deown*(s-fown))*fown/math.sqr(s);
+                            offs1 = offs1+chunksize;
+                            offs2 = offs2+chunksize;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Regression network with sum-of-squares function.
+                //
+                // For each NOut of last neurons:
+                // * calculate difference between actual and desired output
+                // * calculate dError/dOut for this neuron (proportional to difference)
+                // * store in in last 4 components of entry (these values are used
+                //   to start backpropagation)
+                // * update error
+                //
+                for(i=0; i<=nout-1; i++)
+                {
+                    v0 = network.columnsigmas[nin+i];
+                    v1 = network.columnmeans[nin+i];
+                    entryoffs = entrysize*(ntotal-nout+i);
+                    offs0 = entryoffs;
+                    offs1 = entryoffs+derroroffs;
+                    for(j=0; j<=csize-1; j++)
+                    {
+                        v = batch4buf[offs0+j]*v0+v1-xy[cstart+j,nin+i];
+                        batch4buf[offs1+j] = v*v0;
+                        e = e+v*v/2;
+                    }
+                }
+            }
+            
+            //
+            // Backpropagation
+            //
+            for(neuronidx=ntotal-1; neuronidx>=0; neuronidx--)
+            {
+                entryoffs = entrysize*neuronidx;
+                offs = istart+neuronidx*nfieldwidth;
+                neurontype = network.structinfo[offs+0];
+                if( neurontype>0 || neurontype==-5 )
+                {
+                    
+                    //
+                    // Activation function
+                    //
+                    srcneuronidx = network.structinfo[offs+2];
+                    srcentryoffs = entrysize*srcneuronidx;
+                    offs0 = srcentryoffs+derroroffs;
+                    offs1 = entryoffs+derroroffs;
+                    offs2 = entryoffs+dfoffs;
+                    batch4buf[offs0+0] = batch4buf[offs0+0]+batch4buf[offs1+0]*batch4buf[offs2+0];
+                    batch4buf[offs0+1] = batch4buf[offs0+1]+batch4buf[offs1+1]*batch4buf[offs2+1];
+                    batch4buf[offs0+2] = batch4buf[offs0+2]+batch4buf[offs1+2]*batch4buf[offs2+2];
+                    batch4buf[offs0+3] = batch4buf[offs0+3]+batch4buf[offs1+3]*batch4buf[offs2+3];
+                    continue;
+                }
+                if( neurontype==0 )
+                {
+                    
+                    //
+                    // Adaptive summator
+                    //
+                    nweights = network.structinfo[offs+1];
+                    srcneuronidx = network.structinfo[offs+2];
+                    srcentryoffs = entrysize*srcneuronidx;
+                    srcweightidx = network.structinfo[offs+3];
+                    v0 = batch4buf[entryoffs+derroroffs+0];
+                    v1 = batch4buf[entryoffs+derroroffs+1];
+                    v2 = batch4buf[entryoffs+derroroffs+2];
+                    v3 = batch4buf[entryoffs+derroroffs+3];
+                    for(j=0; j<=nweights-1; j++)
+                    {
+                        offs0 = srcentryoffs;
+                        offs1 = srcentryoffs+derroroffs;
+                        v = network.weights[srcweightidx];
+                        hpcbuf[srcweightidx] = hpcbuf[srcweightidx]+batch4buf[offs0+0]*v0+batch4buf[offs0+1]*v1+batch4buf[offs0+2]*v2+batch4buf[offs0+3]*v3;
+                        batch4buf[offs1+0] = batch4buf[offs1+0]+v*v0;
+                        batch4buf[offs1+1] = batch4buf[offs1+1]+v*v1;
+                        batch4buf[offs1+2] = batch4buf[offs1+2]+v*v2;
+                        batch4buf[offs1+3] = batch4buf[offs1+3]+v*v3;
+                        srcentryoffs = srcentryoffs+entrysize;
+                        srcweightidx = srcweightidx+1;
+                    }
+                    continue;
+                }
+                if( neurontype<0 )
+                {
+                    bflag = false;
+                    if( (neurontype==-2 || neurontype==-3) || neurontype==-4 )
+                    {
+                        
+                        //
+                        // Special neuron type, no back-propagation required
+                        //
+                        bflag = true;
+                    }
+                    alglib.ap.assert(bflag, "MLPInternalCalculateGradient: unknown neuron type!");
+                    continue;
+                }
+            }
+        }
+
+
+        private static void mlpchunkedprocess(multilayerperceptron network,
+            double[,] xy,
+            int cstart,
+            int csize,
+            double[] batch4buf,
+            double[] hpcbuf,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 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;
+            bool bflag = new bool();
+            int istart = 0;
+            int entrysize = 0;
+            int entryoffs = 0;
+            int neuronidx = 0;
+            int srcentryoffs = 0;
+            int srcneuronidx = 0;
+            int srcweightidx = 0;
+            int neurontype = 0;
+            int nweights = 0;
+            int offs0 = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double v3 = 0;
+            double s0 = 0;
+            double s1 = 0;
+            double s2 = 0;
+            double s3 = 0;
+            int chunksize = 0;
+
+            chunksize = 4;
+            alglib.ap.assert(csize<=chunksize, "MLPChunkedProcess: internal error (CSize>ChunkSize)");
+            
+            //
+            // Try to use HPC core, if possible
+            //
+            if( hpccores.hpcchunkedprocess(network.weights, network.structinfo, network.columnmeans, network.columnsigmas, xy, cstart, csize, batch4buf, hpcbuf, _params) )
+            {
+                return;
+            }
+            
+            //
+            // Read network geometry, prepare data
+            //
+            nin = network.structinfo[1];
+            nout = network.structinfo[2];
+            ntotal = network.structinfo[3];
+            istart = network.structinfo[5];
+            entrysize = 4;
+            
+            //
+            // Fill Batch4Buf by zeros.
+            //
+            // THIS STAGE IS VERY IMPORTANT!
+            //
+            // We fill all components of entry - neuron values, dF/dNET, dError/dF.
+            // It allows us to easily handle  situations  when  CSize<ChunkSize  by
+            // simply  working  with  ALL  components  of  Batch4Buf,  without ever
+            // looking at CSize.
+            //
+            for(i=0; i<=entrysize*ntotal-1; i++)
+            {
+                batch4buf[i] = 0;
+            }
+            
+            //
+            // Forward pass:
+            // 1. Load data into Batch4Buf. If CSize<ChunkSize, data are padded by zeros.
+            // 2. Perform forward pass through network
+            //
+            for(i=0; i<=nin-1; i++)
+            {
+                entryoffs = entrysize*i;
+                for(j=0; j<=csize-1; j++)
+                {
+                    if( (double)(network.columnsigmas[i])!=(double)(0) )
+                    {
+                        batch4buf[entryoffs+j] = (xy[cstart+j,i]-network.columnmeans[i])/network.columnsigmas[i];
+                    }
+                    else
+                    {
+                        batch4buf[entryoffs+j] = xy[cstart+j,i]-network.columnmeans[i];
+                    }
+                }
+            }
+            for(neuronidx=0; neuronidx<=ntotal-1; neuronidx++)
+            {
+                entryoffs = entrysize*neuronidx;
+                offs = istart+neuronidx*nfieldwidth;
+                neurontype = network.structinfo[offs+0];
+                if( neurontype>0 || neurontype==-5 )
+                {
+                    
+                    //
+                    // "activation function" neuron, which takes value of neuron SrcNeuronIdx
+                    // and applies activation function to it.
+                    //
+                    // This neuron has no weights and no tunable parameters.
+                    //
+                    srcneuronidx = network.structinfo[offs+2];
+                    srcentryoffs = entrysize*srcneuronidx;
+                    mlpactivationfunction(batch4buf[srcentryoffs+0], neurontype, ref f, ref df, ref d2f, _params);
+                    batch4buf[entryoffs+0] = f;
+                    mlpactivationfunction(batch4buf[srcentryoffs+1], neurontype, ref f, ref df, ref d2f, _params);
+                    batch4buf[entryoffs+1] = f;
+                    mlpactivationfunction(batch4buf[srcentryoffs+2], neurontype, ref f, ref df, ref d2f, _params);
+                    batch4buf[entryoffs+2] = f;
+                    mlpactivationfunction(batch4buf[srcentryoffs+3], neurontype, ref f, ref df, ref d2f, _params);
+                    batch4buf[entryoffs+3] = f;
+                    continue;
+                }
+                if( neurontype==0 )
+                {
+                    
+                    //
+                    // "adaptive summator" neuron, whose output is a weighted sum of inputs.
+                    // It has weights, but has no activation function.
+                    //
+                    nweights = network.structinfo[offs+1];
+                    srcneuronidx = network.structinfo[offs+2];
+                    srcentryoffs = entrysize*srcneuronidx;
+                    srcweightidx = network.structinfo[offs+3];
+                    v0 = 0;
+                    v1 = 0;
+                    v2 = 0;
+                    v3 = 0;
+                    for(j=0; j<=nweights-1; j++)
+                    {
+                        v = network.weights[srcweightidx];
+                        srcweightidx = srcweightidx+1;
+                        v0 = v0+v*batch4buf[srcentryoffs+0];
+                        v1 = v1+v*batch4buf[srcentryoffs+1];
+                        v2 = v2+v*batch4buf[srcentryoffs+2];
+                        v3 = v3+v*batch4buf[srcentryoffs+3];
+                        srcentryoffs = srcentryoffs+entrysize;
+                    }
+                    batch4buf[entryoffs+0] = v0;
+                    batch4buf[entryoffs+1] = v1;
+                    batch4buf[entryoffs+2] = v2;
+                    batch4buf[entryoffs+3] = v3;
+                    continue;
+                }
+                if( neurontype<0 )
+                {
+                    bflag = false;
+                    if( neurontype==-2 )
+                    {
+                        
+                        //
+                        // Input neuron, left unchanged
+                        //
+                        bflag = true;
+                    }
+                    if( neurontype==-3 )
+                    {
+                        
+                        //
+                        // "-1" neuron
+                        //
+                        batch4buf[entryoffs+0] = -1;
+                        batch4buf[entryoffs+1] = -1;
+                        batch4buf[entryoffs+2] = -1;
+                        batch4buf[entryoffs+3] = -1;
+                        bflag = true;
+                    }
+                    if( neurontype==-4 )
+                    {
+                        
+                        //
+                        // "0" neuron
+                        //
+                        batch4buf[entryoffs+0] = 0;
+                        batch4buf[entryoffs+1] = 0;
+                        batch4buf[entryoffs+2] = 0;
+                        batch4buf[entryoffs+3] = 0;
+                        bflag = true;
+                    }
+                    alglib.ap.assert(bflag, "MLPChunkedProcess: internal error - unknown neuron type!");
+                    continue;
+                }
+            }
+            
+            //
+            // SOFTMAX normalization or scaling.
+            //
+            alglib.ap.assert(network.structinfo[6]==0 || network.structinfo[6]==1, "MLPChunkedProcess: unknown normalization type!");
+            if( network.structinfo[6]==1 )
+            {
+                
+                //
+                // SOFTMAX-normalized network.
+                //
+                // First,  calculate (V0,V1,V2,V3)  -  component-wise  maximum
+                // of output neurons. This vector of maximum  values  will  be
+                // used for normalization  of  outputs  prior  to  calculating
+                // exponentials.
+                //
+                // NOTE: the only purpose of this stage is to prevent overflow
+                //       during calculation of exponentials.  With  this stage
+                //       we  make  sure  that  all exponentials are calculated
+                //       with non-positive argument. If you load (0,0,0,0)  to
+                //       (V0,V1,V2,V3), your program will continue  working  -
+                //       although with less robustness.
+                //
+                entryoffs = entrysize*(ntotal-nout);
+                v0 = batch4buf[entryoffs+0];
+                v1 = batch4buf[entryoffs+1];
+                v2 = batch4buf[entryoffs+2];
+                v3 = batch4buf[entryoffs+3];
+                entryoffs = entryoffs+entrysize;
+                for(i=1; i<=nout-1; i++)
+                {
+                    v = batch4buf[entryoffs+0];
+                    if( v>v0 )
+                    {
+                        v0 = v;
+                    }
+                    v = batch4buf[entryoffs+1];
+                    if( v>v1 )
+                    {
+                        v1 = v;
+                    }
+                    v = batch4buf[entryoffs+2];
+                    if( v>v2 )
+                    {
+                        v2 = v;
+                    }
+                    v = batch4buf[entryoffs+3];
+                    if( v>v3 )
+                    {
+                        v3 = v;
+                    }
+                    entryoffs = entryoffs+entrysize;
+                }
+                
+                //
+                // Then,  calculate exponentials and place them to part of the
+                // array which  is  located  past  the  last  entry.  We  also
+                // calculate sum of exponentials.
+                //
+                entryoffs = entrysize*(ntotal-nout);
+                offs0 = entrysize*ntotal;
+                s0 = 0;
+                s1 = 0;
+                s2 = 0;
+                s3 = 0;
+                for(i=0; i<=nout-1; i++)
+                {
+                    v = Math.Exp(batch4buf[entryoffs+0]-v0);
+                    s0 = s0+v;
+                    batch4buf[offs0+0] = v;
+                    v = Math.Exp(batch4buf[entryoffs+1]-v1);
+                    s1 = s1+v;
+                    batch4buf[offs0+1] = v;
+                    v = Math.Exp(batch4buf[entryoffs+2]-v2);
+                    s2 = s2+v;
+                    batch4buf[offs0+2] = v;
+                    v = Math.Exp(batch4buf[entryoffs+3]-v3);
+                    s3 = s3+v;
+                    batch4buf[offs0+3] = v;
+                    entryoffs = entryoffs+entrysize;
+                    offs0 = offs0+chunksize;
+                }
+                
+                //
+                // Write SOFTMAX-normalized values to the output array.
+                //
+                offs0 = entrysize*ntotal;
+                for(i=0; i<=nout-1; i++)
+                {
+                    if( csize>0 )
+                    {
+                        xy[cstart+0,nin+i] = batch4buf[offs0+0]/s0;
+                    }
+                    if( csize>1 )
+                    {
+                        xy[cstart+1,nin+i] = batch4buf[offs0+1]/s1;
+                    }
+                    if( csize>2 )
+                    {
+                        xy[cstart+2,nin+i] = batch4buf[offs0+2]/s2;
+                    }
+                    if( csize>3 )
+                    {
+                        xy[cstart+3,nin+i] = batch4buf[offs0+3]/s3;
+                    }
+                    offs0 = offs0+chunksize;
+                }
+            }
+            else
+            {
+                
+                //
+                // Regression network with sum-of-squares function.
+                //
+                // For each NOut of last neurons:
+                // * calculate difference between actual and desired output
+                // * calculate dError/dOut for this neuron (proportional to difference)
+                // * store in in last 4 components of entry (these values are used
+                //   to start backpropagation)
+                // * update error
+                //
+                for(i=0; i<=nout-1; i++)
+                {
+                    v0 = network.columnsigmas[nin+i];
+                    v1 = network.columnmeans[nin+i];
+                    entryoffs = entrysize*(ntotal-nout+i);
+                    for(j=0; j<=csize-1; j++)
+                    {
+                        xy[cstart+j,nin+i] = batch4buf[entryoffs+j]*v0+v1;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Returns T*Ln(T/Z), guarded against overflow/underflow.
+        Internal subroutine.
+        *************************************************************************/
+        private static double safecrossentropy(double t,
+            double z,
+            alglib.xparams _params)
+        {
+            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;
+        }
+
+
+        /*************************************************************************
+        This function performs backward pass of neural network randimization:
+        * it assumes that Network.Weights stores standard deviation of weights
+          (weights are not generated yet, only their deviations are present)
+        * it sets deviations of weights which feed NeuronIdx-th neuron to specified value
+        * it recursively passes to deeper neuron and modifies their weights
+        * it stops after encountering nonlinear neurons, linear activation function,
+          input neurons, "0" and "-1" neurons
+
+          -- ALGLIB --
+             Copyright 27.06.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void randomizebackwardpass(multilayerperceptron network,
+            int neuronidx,
+            double v,
+            alglib.xparams _params)
+        {
+            int istart = 0;
+            int neurontype = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int w1 = 0;
+            int w2 = 0;
+            int offs = 0;
+            int i = 0;
+
+            istart = network.structinfo[5];
+            neurontype = network.structinfo[istart+neuronidx*nfieldwidth+0];
+            if( neurontype==-2 )
+            {
+                
+                //
+                // Input neuron - stop
+                //
+                return;
+            }
+            if( neurontype==-3 )
+            {
+                
+                //
+                // "-1" neuron: stop
+                //
+                return;
+            }
+            if( neurontype==-4 )
+            {
+                
+                //
+                // "0" neuron: stop
+                //
+                return;
+            }
+            if( neurontype==0 )
+            {
+                
+                //
+                // Adaptive summator neuron:
+                // * modify deviations of its weights
+                // * recursively call this function for its inputs
+                //
+                offs = istart+neuronidx*nfieldwidth;
+                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=w1; i<=w2; i++)
+                {
+                    network.weights[i] = v;
+                }
+                for(i=n1; i<=n2; i++)
+                {
+                    randomizebackwardpass(network, i, v, _params);
+                }
+                return;
+            }
+            if( neurontype==-5 )
+            {
+                
+                //
+                // Linear activation function: stop
+                //
+                return;
+            }
+            if( neurontype>0 )
+            {
+                
+                //
+                // Nonlinear activation function: stop
+                //
+                return;
+            }
+            alglib.ap.assert(false, "RandomizeBackwardPass: unexpected neuron type");
+        }
+
+
+    }
+    public class lda
+    {
+        /*************************************************************************
+        Multiclass Fisher LDA
+
+        Subroutine finds coefficients of linear combination which optimally separates
+        training set on classes.
+
+        COMMERCIAL EDITION OF ALGLIB:
+
+          ! Commercial version of ALGLIB includes two important  improvements   of
+          ! this function, which can be used from C++ and C#:
+          ! * Intel MKL support (lightweight Intel MKL is shipped with ALGLIB)
+          ! * multithreading support
+          !
+          ! Intel MKL gives approximately constant  (with  respect  to  number  of
+          ! worker threads) acceleration factor which depends on CPU  being  used,
+          ! problem  size  and  "baseline"  ALGLIB  edition  which  is  used   for
+          ! comparison. Best results are achieved  for  high-dimensional  problems
+          ! (NVars is at least 256).
+          !
+          ! Multithreading is used to  accelerate  initial  phase  of  LDA,  which
+          ! includes calculation of products of large matrices.  Again,  for  best
+          ! efficiency problem must be high-dimensional.
+          !
+          ! Generally, commercial ALGLIB is several times faster than  open-source
+          ! generic C edition, and many times faster than open-source C# edition.
+          !
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            double[,] w2 = new double[0,0];
+            int i_ = 0;
+
+            info = 0;
+            w = new double[0];
+
+            fisherldan(xy, npoints, nvars, nclasses, ref info, ref w2, _params);
+            if( info>0 )
+            {
+                w = new double[nvars];
+                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).
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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[,] xyc = 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, nvars];
+                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];
+            work = new double[Math.Max(nvars, npoints)+1];
+            xyc = new double[npoints, nvars];
+            
+            //
+            // Convert class labels from reals to integers (just for convenience)
+            //
+            c = new int[npoints];
+            for(i=0; i<=npoints-1; i++)
+            {
+                c[i] = (int)Math.Round(xy[i,nvars]);
+            }
+            
+            //
+            // Calculate class sizes, class means
+            //
+            mu = new double[nvars];
+            muc = new double[nclasses, nvars];
+            nc = new int[nclasses];
+            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, nvars];
+            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_++)
+                {
+                    xyc[k,i_] = xy[k,i_];
+                }
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    xyc[k,i_] = xyc[k,i_] - mu[i_];
+                }
+            }
+            ablas.rmatrixgemm(nvars, nvars, npoints, 1.0, xyc, 0, 0, 1, xyc, 0, 0, 0, 0.0, st, 0, 0, _params);
+            
+            //
+            // Create SW matrix
+            //
+            sw = new double[nvars, nvars];
+            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_++)
+                {
+                    xyc[k,i_] = xy[k,i_];
+                }
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    xyc[k,i_] = xyc[k,i_] - muc[c[k],i_];
+                }
+            }
+            ablas.rmatrixgemm(nvars, nvars, npoints, 1.0, xyc, 0, 0, 1, xyc, 0, 0, 0, 0.0, sw, 0, 0, _params);
+            
+            //
+            // 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, _params) )
+            {
+                info = -4;
+                return;
+            }
+            w = new double[nvars, nvars];
+            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, nvars-m+1];
+                ablas.rmatrixgemm(npoints, nvars-m, nvars, 1.0, xy, 0, 0, 0, z, 0, m, 0, 0.0, xyproj, 0, 0, _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    xyproj[i,nvars-m] = xy[i,nvars];
+                }
+                fisherldan(xyproj, npoints, nvars-m, nclasses, ref info, ref wproj, _params);
+                if( info<0 )
+                {
+                    return;
+                }
+                ablas.rmatrixgemm(nvars, nvars-m, nvars-m, 1.0, z, 0, m, 0, wproj, 0, 0, 0, 0.0, w, 0, 0, _params);
+                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, nvars];
+                a = new double[nvars, nvars];
+                ablas.rmatrixgemm(nvars, nvars, nvars, 1.0, sw, 0, 0, 0, z, 0, 0, 0, 0.0, tm, 0, 0, _params);
+                ablas.rmatrixgemm(nvars, nvars, nvars, 1.0, z, 0, 0, 1, tm, 0, 0, 0, 0.0, a, 0, 0, _params);
+                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, _params) )
+                {
+                    info = -4;
+                    return;
+                }
+                for(i=0; i<=nvars-1; i++)
+                {
+                    for(k=0; k<=nvars-1; k++)
+                    {
+                        z2[i,k] = z2[i,k]/Math.Sqrt(d[i]);
+                    }
+                }
+                ablas.rmatrixgemm(nvars, nvars, nvars, 1.0, z, 0, 0, 0, z2, 0, 0, 0, 0.0, w, 0, 0, _params);
+            }
+            
+            //
+            // 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 ssa
+    {
+        /*************************************************************************
+        This object stores state of the SSA model.
+
+        You should use ALGLIB functions to work with this object.
+        *************************************************************************/
+        public class ssamodel : apobject
+        {
+            public int nsequences;
+            public int[] sequenceidx;
+            public double[] sequencedata;
+            public int algotype;
+            public int windowwidth;
+            public int rtpowerup;
+            public int topk;
+            public int precomputedwidth;
+            public int precomputednbasis;
+            public double[,] precomputedbasis;
+            public int defaultsubspaceits;
+            public int memorylimit;
+            public bool arebasisandsolvervalid;
+            public double[,] basis;
+            public double[,] basist;
+            public double[] sv;
+            public double[] forecasta;
+            public int nbasis;
+            public evd.eigsubspacestate solver;
+            public double[,] xxt;
+            public hqrnd.hqrndstate rs;
+            public int rngseed;
+            public int[] rtqueue;
+            public int rtqueuecnt;
+            public int rtqueuechunk;
+            public int dbgcntevd;
+            public double[] tmp0;
+            public double[] tmp1;
+            public evd.eigsubspacereport solverrep;
+            public double[] alongtrend;
+            public double[] alongnoise;
+            public double[,] aseqtrajectory;
+            public double[,] aseqtbproduct;
+            public int[] aseqcounts;
+            public double[] fctrend;
+            public double[] fcnoise;
+            public double[,] fctrendm;
+            public double[,] uxbatch;
+            public int uxbatchwidth;
+            public int uxbatchsize;
+            public int uxbatchlimit;
+            public ssamodel()
+            {
+                init();
+            }
+            public override void init()
+            {
+                sequenceidx = new int[0];
+                sequencedata = new double[0];
+                precomputedbasis = new double[0,0];
+                basis = new double[0,0];
+                basist = new double[0,0];
+                sv = new double[0];
+                forecasta = new double[0];
+                solver = new evd.eigsubspacestate();
+                xxt = new double[0,0];
+                rs = new hqrnd.hqrndstate();
+                rtqueue = new int[0];
+                tmp0 = new double[0];
+                tmp1 = new double[0];
+                solverrep = new evd.eigsubspacereport();
+                alongtrend = new double[0];
+                alongnoise = new double[0];
+                aseqtrajectory = new double[0,0];
+                aseqtbproduct = new double[0,0];
+                aseqcounts = new int[0];
+                fctrend = new double[0];
+                fcnoise = new double[0];
+                fctrendm = new double[0,0];
+                uxbatch = new double[0,0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                ssamodel _result = new ssamodel();
+                _result.nsequences = nsequences;
+                _result.sequenceidx = (int[])sequenceidx.Clone();
+                _result.sequencedata = (double[])sequencedata.Clone();
+                _result.algotype = algotype;
+                _result.windowwidth = windowwidth;
+                _result.rtpowerup = rtpowerup;
+                _result.topk = topk;
+                _result.precomputedwidth = precomputedwidth;
+                _result.precomputednbasis = precomputednbasis;
+                _result.precomputedbasis = (double[,])precomputedbasis.Clone();
+                _result.defaultsubspaceits = defaultsubspaceits;
+                _result.memorylimit = memorylimit;
+                _result.arebasisandsolvervalid = arebasisandsolvervalid;
+                _result.basis = (double[,])basis.Clone();
+                _result.basist = (double[,])basist.Clone();
+                _result.sv = (double[])sv.Clone();
+                _result.forecasta = (double[])forecasta.Clone();
+                _result.nbasis = nbasis;
+                _result.solver = (evd.eigsubspacestate)solver.make_copy();
+                _result.xxt = (double[,])xxt.Clone();
+                _result.rs = (hqrnd.hqrndstate)rs.make_copy();
+                _result.rngseed = rngseed;
+                _result.rtqueue = (int[])rtqueue.Clone();
+                _result.rtqueuecnt = rtqueuecnt;
+                _result.rtqueuechunk = rtqueuechunk;
+                _result.dbgcntevd = dbgcntevd;
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmp1 = (double[])tmp1.Clone();
+                _result.solverrep = (evd.eigsubspacereport)solverrep.make_copy();
+                _result.alongtrend = (double[])alongtrend.Clone();
+                _result.alongnoise = (double[])alongnoise.Clone();
+                _result.aseqtrajectory = (double[,])aseqtrajectory.Clone();
+                _result.aseqtbproduct = (double[,])aseqtbproduct.Clone();
+                _result.aseqcounts = (int[])aseqcounts.Clone();
+                _result.fctrend = (double[])fctrend.Clone();
+                _result.fcnoise = (double[])fcnoise.Clone();
+                _result.fctrendm = (double[,])fctrendm.Clone();
+                _result.uxbatch = (double[,])uxbatch.Clone();
+                _result.uxbatchwidth = uxbatchwidth;
+                _result.uxbatchsize = uxbatchsize;
+                _result.uxbatchlimit = uxbatchlimit;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        This function creates SSA model object.  Right after creation model is  in
+        "dummy" mode - you can add data,  but   analyzing/prediction  will  return
+        just zeros (it assumes that basis is empty).
+
+        HOW TO USE SSA MODEL:
+
+        1. create model with ssacreate()
+        2. add data with one/many ssaaddsequence() calls
+        3. choose SSA algorithm with one of ssasetalgo...() functions:
+           * ssasetalgotopkdirect() for direct one-run analysis
+           * ssasetalgotopkrealtime() for algorithm optimized for many  subsequent
+             runs with warm-start capabilities
+           * ssasetalgoprecomputed() for user-supplied basis
+        4. set window width with ssasetwindow()
+        5. perform one of the analysis-related activities:
+           a) call ssagetbasis() to get basis
+           b) call ssaanalyzelast() ssaanalyzesequence() or ssaanalyzelastwindow()
+              to perform analysis (trend/noise separation)
+           c) call  one  of   the   forecasting   functions  (ssaforecastlast() or
+              ssaforecastsequence()) to perform prediction; alternatively, you can
+              extract linear recurrence coefficients with ssagetlrr().
+           SSA analysis will be performed during first  call  to  analysis-related
+           function. SSA model is smart enough to track all changes in the dataset
+           and  model  settings,  to  cache  previously  computed  basis  and   to
+           re-evaluate basis only when necessary.
+              
+        Additionally, if your setting involves constant stream  of  incoming data,
+        you can perform quick update already calculated  model  with  one  of  the
+        incremental   append-and-update   functions:  ssaappendpointandupdate() or
+        ssaappendsequenceandupdate().
+
+        NOTE: steps (2), (3), (4) can be performed in arbitrary order.
+           
+        INPUT PARAMETERS:
+            none
+
+        OUTPUT PARAMETERS:
+            S               -   structure which stores model state
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssacreate(ssamodel s,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Model data, algorithms and settings
+            //
+            s.nsequences = 0;
+            s.sequenceidx = new int[1];
+            s.sequenceidx[0] = 0;
+            s.algotype = 0;
+            s.windowwidth = 1;
+            s.rtpowerup = 1;
+            s.arebasisandsolvervalid = false;
+            s.rngseed = 1;
+            s.defaultsubspaceits = 10;
+            s.memorylimit = 50000000;
+            
+            //
+            // Debug counters
+            //
+            s.dbgcntevd = 0;
+        }
+
+
+        /*************************************************************************
+        This function sets window width for SSA model. You should call  it  before
+        analysis phase. Default window width is 1 (not for real use).
+
+        Special notes:
+        * this function call can be performed at any moment before  first call  to
+          analysis-related functions
+        * changing window width invalidates internally stored basis; if you change
+          window width AFTER you call analysis-related  function,  next  analysis
+          phase will require re-calculation of  the  basis  according  to  current
+          algorithm.
+        * calling this function with exactly  same window width as current one has
+          no effect
+        * if you specify window width larger  than any data sequence stored in the
+          model, analysis will return zero basis.
+           
+        INPUT PARAMETERS:
+            S               -   SSA model created with ssacreate()
+            WindowWidth     -   >=1, new window width
+
+        OUTPUT PARAMETERS:
+            S               -   SSA model, updated
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssasetwindow(ssamodel s,
+            int windowwidth,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(windowwidth>=1, "SSASetWindow: WindowWidth<1");
+            if( windowwidth==s.windowwidth )
+            {
+                return;
+            }
+            s.windowwidth = windowwidth;
+            s.arebasisandsolvervalid = false;
+        }
+
+
+        /*************************************************************************
+        This  function  sets  seed  which  is used to initialize internal RNG when
+        we make pseudorandom decisions on model updates.
+
+        By default, deterministic seed is used - which results in same sequence of
+        pseudorandom decisions every time you run SSA model. If you  specify  non-
+        deterministic seed value, then SSA  model  may  return  slightly different
+        results after each run.
+
+        This function can be useful when you have several SSA models updated  with
+        sseappendpointandupdate() called with 0<UpdateIts<1 (fractional value) and
+        due to performance limitations want them to perform updates  at  different
+        moments.
+
+        INPUT PARAMETERS:
+            S       -   SSA model
+            Seed    -   seed:
+                        * positive values = use deterministic seed for each run of
+                          algorithms which depend on random initialization
+                        * zero or negative values = use non-deterministic seed
+
+          -- ALGLIB --
+             Copyright 03.11.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssasetseed(ssamodel s,
+            int seed,
+            alglib.xparams _params)
+        {
+            s.rngseed = seed;
+        }
+
+
+        /*************************************************************************
+        This function sets length of power-up cycle for real-time algorithm.
+
+        By default, this algorithm performs costly O(N*WindowWidth^2)  init  phase
+        followed by full run of truncated  EVD.  However,  if  you  are  ready  to
+        live with a bit lower-quality basis during first few iterations,  you  can
+        split this O(N*WindowWidth^2) initialization  between  several  subsequent
+        append-and-update rounds. It results in better latency of the algorithm.
+
+        This function invalidates basis/solver, next analysis call will result  in
+        full recalculation of everything.
+
+        INPUT PARAMETERS:
+            S       -   SSA model
+            PWLen   -   length of the power-up stage:
+                        * 0 means that no power-up is requested
+                        * 1 is the same as 0
+                        * >1 means that delayed power-up is performed
+
+          -- ALGLIB --
+             Copyright 03.11.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssasetpoweruplength(ssamodel s,
+            int pwlen,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(pwlen>=0, "SSASetPowerUpLength: PWLen<0");
+            s.rtpowerup = Math.Max(pwlen, 1);
+            s.arebasisandsolvervalid = false;
+        }
+
+
+        /*************************************************************************
+        This function sets memory limit of SSA analysis.
+
+        Straightforward SSA with sequence length T and window width W needs O(T*W)
+        memory. It is possible to reduce memory consumption by splitting task into
+        smaller chunks.
+
+        Thus function allows you to specify approximate memory limit (measured  in
+        double precision numbers used for buffers). Actual memory consumption will
+        be comparable to the number specified by you.
+
+        Default memory limit is 50.000.000 (400Mbytes) in current version.
+
+        INPUT PARAMETERS:
+            S       -   SSA model
+            MemLimit-   memory limit, >=0. Zero value means no limit.
+
+          -- ALGLIB --
+             Copyright 20.12.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssasetmemorylimit(ssamodel s,
+            int memlimit,
+            alglib.xparams _params)
+        {
+            if( memlimit<0 )
+            {
+                memlimit = 0;
+            }
+            s.memorylimit = memlimit;
+        }
+
+
+        /*************************************************************************
+        This function adds data sequence to SSA  model.  Only   single-dimensional
+        sequences are supported.
+
+        What is a sequences? Following definitions/requirements apply:
+        * a sequence  is  an  array of  values  measured  in  subsequent,  equally
+          separated time moments (ticks).
+        * you may have many sequences  in your  dataset;  say,  one  sequence  may
+          correspond to one trading session.
+        * sequence length should be larger  than current  window  length  (shorter
+          sequences will be ignored during analysis).
+        * analysis is performed within a  sequence; different  sequences  are  NOT
+          stacked together to produce one large contiguous stream of data.
+        * analysis is performed for all  sequences at once, i.e. same set of basis
+          vectors is computed for all sequences
+          
+        INCREMENTAL ANALYSIS
+          
+        This function is non intended for  incremental updates of previously found
+        SSA basis. Calling it invalidates  all previous analysis results (basis is
+        reset and will be recalculated from zero during next analysis).
+
+        If  you  want  to  perform   incremental/real-time  SSA,  consider   using
+        following functions:
+        * ssaappendpointandupdate() for appending one point
+        * ssaappendsequenceandupdate() for appending new sequence
+           
+        INPUT PARAMETERS:
+            S               -   SSA model created with ssacreate()
+            X               -   array[N], data, can be larger (additional values
+                                are ignored)
+            N               -   data length, can be automatically determined from
+                                the array length. N>=0.
+
+        OUTPUT PARAMETERS:
+            S               -   SSA model, updated
+            
+        NOTE: you can clear dataset with ssacleardata()
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssaaddsequence(ssamodel s,
+            double[] x,
+            int n,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int offs = 0;
+
+            alglib.ap.assert(n>=0, "SSAAddSequence: N<0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "SSAAddSequence: X is too short");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "SSAAddSequence: X contains infinities NANs");
+            
+            //
+            // Invalidate model
+            //
+            s.arebasisandsolvervalid = false;
+            
+            //
+            // Add sequence
+            //
+            apserv.ivectorgrowto(ref s.sequenceidx, s.nsequences+2, _params);
+            s.sequenceidx[s.nsequences+1] = s.sequenceidx[s.nsequences]+n;
+            apserv.rvectorgrowto(ref s.sequencedata, s.sequenceidx[s.nsequences+1], _params);
+            offs = s.sequenceidx[s.nsequences];
+            for(i=0; i<=n-1; i++)
+            {
+                s.sequencedata[offs+i] = x[i];
+            }
+            apserv.inc(ref s.nsequences, _params);
+        }
+
+
+        /*************************************************************************
+        This function appends single point to last data sequence stored in the SSA
+        model and tries to update model in the  incremental  manner  (if  possible
+        with current algorithm).
+
+        If you want to add more than one point at once:
+        * if you want to add M points to the same sequence, perform M-1 calls with
+          UpdateIts parameter set to 0.0, and last call with non-zero UpdateIts.
+        * if you want to add new sequence, use ssaappendsequenceandupdate()
+
+        Running time of this function does NOT depend on  dataset  size,  only  on
+        window width and number of singular vectors. Depending on algorithm  being
+        used, incremental update has complexity:
+        * for top-K real time   - O(UpdateIts*K*Width^2), with fractional UpdateIts
+        * for top-K direct      - O(Width^3) for any non-zero UpdateIts
+        * for precomputed basis - O(1), no update is performed
+           
+        INPUT PARAMETERS:
+            S               -   SSA model created with ssacreate()
+            X               -   new point
+            UpdateIts       -   >=0,  floating  point (!)  value,  desired  update
+                                frequency:
+                                * zero value means that point is  stored,  but  no
+                                  update is performed
+                                * integer part of the value means  that  specified
+                                  number of iterations is always performed
+                                * fractional part of  the  value  means  that  one
+                                  iteration is performed with this probability.
+                                  
+                                Recommended value: 0<UpdateIts<=1.  Values  larger
+                                than 1 are VERY seldom  needed.  If  your  dataset
+                                changes slowly, you can set it  to  0.1  and  skip
+                                90% of updates.
+                                
+                                In any case, no information is lost even with zero
+                                value of UpdateIts! It will be  incorporated  into
+                                model, sooner or later.
+
+        OUTPUT PARAMETERS:
+            S               -   SSA model, updated
+
+        NOTE: this function uses internal  RNG  to  handle  fractional  values  of
+              UpdateIts. By default it  is  initialized  with  fixed  seed  during
+              initial calculation of basis. Thus subsequent calls to this function
+              will result in the same sequence of pseudorandom decisions.
+              
+              However, if  you  have  several  SSA  models  which  are  calculated
+              simultaneously, and if you want to reduce computational  bottlenecks
+              by performing random updates at random moments, then fixed  seed  is
+              not an option - all updates will fire at same moments.
+              
+              You may change it with ssasetseed() function.
+              
+        NOTE: this function throws an exception if called for empty dataset (there
+              is no "last" sequence to modify).
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssaappendpointandupdate(ssamodel s,
+            double x,
+            double updateits,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(x), "SSAAppendPointAndUpdate: X is not finite");
+            alglib.ap.assert(math.isfinite(updateits), "SSAAppendPointAndUpdate: UpdateIts is not finite");
+            alglib.ap.assert((double)(updateits)>=(double)(0), "SSAAppendPointAndUpdate: UpdateIts<0");
+            alglib.ap.assert(s.nsequences>0, "SSAAppendPointAndUpdate: dataset is empty, no sequence to modify");
+            
+            //
+            // Append point to dataset
+            //
+            apserv.rvectorgrowto(ref s.sequencedata, s.sequenceidx[s.nsequences]+1, _params);
+            s.sequencedata[s.sequenceidx[s.nsequences]] = x;
+            s.sequenceidx[s.nsequences] = s.sequenceidx[s.nsequences]+1;
+            
+            //
+            // Do we have something to analyze? If no, invalidate basis
+            // (just to be sure) and exit.
+            //
+            if( !hassomethingtoanalyze(s, _params) )
+            {
+                s.arebasisandsolvervalid = false;
+                return;
+            }
+            
+            //
+            // Well, we have data to analyze and algorithm set, but basis is
+            // invalid. Let's calculate it from scratch and exit.
+            //
+            if( !s.arebasisandsolvervalid )
+            {
+                updatebasis(s, 0, 0.0, _params);
+                return;
+            }
+            
+            //
+            // Update already computed basis
+            //
+            updatebasis(s, 1, updateits, _params);
+        }
+
+
+        /*************************************************************************
+        This function appends new sequence to dataset stored in the SSA  model and
+        tries to update model in the incremental manner (if possible  with current
+        algorithm).
+
+        Notes:
+        * if you want to add M sequences at once, perform M-1 calls with UpdateIts
+          parameter set to 0.0, and last call with non-zero UpdateIts.
+        * if you want to add just one point, use ssaappendpointandupdate()
+
+        Running time of this function does NOT depend on  dataset  size,  only  on
+        sequence length, window width and number of singular vectors. Depending on
+        algorithm being used, incremental update has complexity:
+        * for top-K real time   - O(UpdateIts*K*Width^2+(NTicks-Width)*Width^2)
+        * for top-K direct      - O(Width^3+(NTicks-Width)*Width^2)
+        * for precomputed basis - O(1), no update is performed
+           
+        INPUT PARAMETERS:
+            S               -   SSA model created with ssacreate()
+            X               -   new sequence, array[NTicks] or larget
+            NTicks          -   >=1, number of ticks in the sequence
+            UpdateIts       -   >=0,  floating  point (!)  value,  desired  update
+                                frequency:
+                                * zero value means that point is  stored,  but  no
+                                  update is performed
+                                * integer part of the value means  that  specified
+                                  number of iterations is always performed
+                                * fractional part of  the  value  means  that  one
+                                  iteration is performed with this probability.
+                                  
+                                Recommended value: 0<UpdateIts<=1.  Values  larger
+                                than 1 are VERY seldom  needed.  If  your  dataset
+                                changes slowly, you can set it  to  0.1  and  skip
+                                90% of updates.
+                                
+                                In any case, no information is lost even with zero
+                                value of UpdateIts! It will be  incorporated  into
+                                model, sooner or later.
+
+        OUTPUT PARAMETERS:
+            S               -   SSA model, updated
+
+        NOTE: this function uses internal  RNG  to  handle  fractional  values  of
+              UpdateIts. By default it  is  initialized  with  fixed  seed  during
+              initial calculation of basis. Thus subsequent calls to this function
+              will result in the same sequence of pseudorandom decisions.
+              
+              However, if  you  have  several  SSA  models  which  are  calculated
+              simultaneously, and if you want to reduce computational  bottlenecks
+              by performing random updates at random moments, then fixed  seed  is
+              not an option - all updates will fire at same moments.
+              
+              You may change it with ssasetseed() function.
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssaappendsequenceandupdate(ssamodel s,
+            double[] x,
+            int nticks,
+            double updateits,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int offs = 0;
+
+            alglib.ap.assert(nticks>=0, "SSAAppendSequenceAndUpdate: NTicks<0");
+            alglib.ap.assert(alglib.ap.len(x)>=nticks, "SSAAppendSequenceAndUpdate: X is too short");
+            alglib.ap.assert(apserv.isfinitevector(x, nticks, _params), "SSAAppendSequenceAndUpdate: X contains infinities NANs");
+            
+            //
+            // Add sequence
+            //
+            apserv.ivectorgrowto(ref s.sequenceidx, s.nsequences+2, _params);
+            s.sequenceidx[s.nsequences+1] = s.sequenceidx[s.nsequences]+nticks;
+            apserv.rvectorgrowto(ref s.sequencedata, s.sequenceidx[s.nsequences+1], _params);
+            offs = s.sequenceidx[s.nsequences];
+            for(i=0; i<=nticks-1; i++)
+            {
+                s.sequencedata[offs+i] = x[i];
+            }
+            apserv.inc(ref s.nsequences, _params);
+            
+            //
+            // Do we have something to analyze? If no, invalidate basis
+            // (just to be sure) and exit.
+            //
+            if( !hassomethingtoanalyze(s, _params) )
+            {
+                s.arebasisandsolvervalid = false;
+                return;
+            }
+            
+            //
+            // Well, we have data to analyze and algorithm set, but basis is
+            // invalid. Let's calculate it from scratch and exit.
+            //
+            if( !s.arebasisandsolvervalid )
+            {
+                updatebasis(s, 0, 0.0, _params);
+                return;
+            }
+            
+            //
+            // Update already computed basis
+            //
+            if( nticks>=s.windowwidth )
+            {
+                updatebasis(s, nticks-s.windowwidth+1, updateits, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        This  function sets SSA algorithm to "precomputed vectors" algorithm.
+
+        This  algorithm  uses  precomputed  set  of  orthonormal  (orthogonal  AND
+        normalized) basis vectors supplied by user. Thus, basis calculation  phase
+        is not performed -  we  already  have  our  basis  -  and  only  analysis/
+        forecasting phase requires actual calculations.
+              
+        This algorithm may handle "append" requests which add just  one/few  ticks
+        to the end of the last sequence in O(1) time.
+
+        NOTE: this algorithm accepts both basis and window  width,  because  these
+              two parameters are naturally aligned.  Calling  this  function  sets
+              window width; if you call ssasetwindow() with  other  window  width,
+              then during analysis stage algorithm will detect conflict and  reset
+              to zero basis.
+
+        INPUT PARAMETERS:
+            S               -   SSA model
+            A               -   array[WindowWidth,NBasis], orthonormalized  basis;
+                                this function does NOT control  orthogonality  and
+                                does NOT perform any kind of  renormalization.  It
+                                is your responsibility to provide it with  correct
+                                basis.
+            WindowWidth     -   window width, >=1
+            NBasis          -   number of basis vectors, 1<=NBasis<=WindowWidth
+
+        OUTPUT PARAMETERS:
+            S               -   updated model
+
+        NOTE: calling this function invalidates basis in all cases.
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssasetalgoprecomputed(ssamodel s,
+            double[,] a,
+            int windowwidth,
+            int nbasis,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(windowwidth>=1, "SSASetAlgoPrecomputed: WindowWidth<1");
+            alglib.ap.assert(nbasis>=1, "SSASetAlgoPrecomputed: NBasis<1");
+            alglib.ap.assert(nbasis<=windowwidth, "SSASetAlgoPrecomputed: NBasis>WindowWidth");
+            alglib.ap.assert(alglib.ap.rows(a)>=windowwidth, "SSASetAlgoPrecomputed: Rows(A)<WindowWidth");
+            alglib.ap.assert(alglib.ap.cols(a)>=nbasis, "SSASetAlgoPrecomputed: Rows(A)<NBasis");
+            alglib.ap.assert(apserv.apservisfinitematrix(a, windowwidth, nbasis, _params), "SSASetAlgoPrecomputed: Rows(A)<NBasis");
+            s.algotype = 1;
+            s.precomputedwidth = windowwidth;
+            s.precomputednbasis = nbasis;
+            s.windowwidth = windowwidth;
+            apserv.rmatrixsetlengthatleast(ref s.precomputedbasis, windowwidth, nbasis, _params);
+            for(i=0; i<=windowwidth-1; i++)
+            {
+                for(j=0; j<=nbasis-1; j++)
+                {
+                    s.precomputedbasis[i,j] = a[i,j];
+                }
+            }
+            s.arebasisandsolvervalid = false;
+        }
+
+
+        /*************************************************************************
+        This  function sets SSA algorithm to "direct top-K" algorithm.
+
+        "Direct top-K" algorithm performs full  SVD  of  the  N*WINDOW  trajectory
+        matrix (hence its name - direct solver  is  used),  then  extracts  top  K
+        components. Overall running time is O(N*WINDOW^2), where N is a number  of
+        ticks in the dataset, WINDOW is window width.
+
+        This algorithm may handle "append" requests which add just  one/few  ticks
+        to the end of the last sequence in O(WINDOW^3) time,  which  is  ~N/WINDOW
+        times faster than re-computing everything from scratch.
+
+        INPUT PARAMETERS:
+            S               -   SSA model
+            TopK            -   number of components to analyze; TopK>=1.
+
+        OUTPUT PARAMETERS:
+            S               -   updated model
+
+
+        NOTE: TopK>WindowWidth is silently decreased to WindowWidth during analysis
+              phase
+              
+        NOTE: calling this function invalidates basis, except  for  the  situation
+              when this algorithm was already set with same parameters.
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssasetalgotopkdirect(ssamodel s,
+            int topk,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(topk>=1, "SSASetAlgoTopKDirect: TopK<1");
+            
+            //
+            // Ignore calls which change nothing
+            //
+            if( s.algotype==2 && s.topk==topk )
+            {
+                return;
+            }
+            
+            //
+            // Update settings, invalidate model
+            //
+            s.algotype = 2;
+            s.topk = topk;
+            s.arebasisandsolvervalid = false;
+        }
+
+
+        /*************************************************************************
+        This function sets SSA algorithm to "top-K real time algorithm". This algo
+        extracts K components with largest singular values.
+
+        It  is  real-time  version  of  top-K  algorithm  which  is  optimized for
+        incremental processing and  fast  start-up. Internally  it  uses  subspace
+        eigensolver for truncated SVD. It results  in  ability  to  perform  quick
+        updates of the basis when only a few points/sequences is added to dataset.
+
+        Performance profile of the algorithm is given below:
+        * O(K*WindowWidth^2) running time for incremental update  of  the  dataset
+          with one of the "append-and-update" functions (ssaappendpointandupdate()
+          or ssaappendsequenceandupdate()).
+        * O(N*WindowWidth^2) running time for initial basis evaluation (N=size  of
+          dataset)
+        * ability  to  split  costly  initialization  across  several  incremental
+          updates of the basis (so called "Power-Up" functionality,  activated  by
+          ssasetpoweruplength() function)
+          
+        INPUT PARAMETERS:
+            S               -   SSA model
+            TopK            -   number of components to analyze; TopK>=1.
+
+        OUTPUT PARAMETERS:
+            S               -   updated model
+
+        NOTE: this  algorithm  is  optimized  for  large-scale  tasks  with  large
+              datasets. On toy problems with just  5-10 points it can return basis
+              which is slightly different from that returned by  direct  algorithm
+              (ssasetalgotopkdirect() function). However, the  difference  becomes
+              negligible as dataset grows.
+
+        NOTE: TopK>WindowWidth is silently decreased to WindowWidth during analysis
+              phase
+              
+        NOTE: calling this function invalidates basis, except  for  the  situation
+              when this algorithm was already set with same parameters.
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssasetalgotopkrealtime(ssamodel s,
+            int topk,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(topk>=1, "SSASetAlgoTopKRealTime: TopK<1");
+            
+            //
+            // Ignore calls which change nothing
+            //
+            if( s.algotype==3 && s.topk==topk )
+            {
+                return;
+            }
+            
+            //
+            // Update settings, invalidate model
+            //
+            s.algotype = 3;
+            s.topk = topk;
+            s.arebasisandsolvervalid = false;
+        }
+
+
+        /*************************************************************************
+        This function clears all data stored in the  model  and  invalidates  all
+        basis components found so far.
+           
+        INPUT PARAMETERS:
+            S               -   SSA model created with ssacreate()
+
+        OUTPUT PARAMETERS:
+            S               -   SSA model, updated
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssacleardata(ssamodel s,
+            alglib.xparams _params)
+        {
+            s.nsequences = 0;
+            s.arebasisandsolvervalid = false;
+        }
+
+
+        /*************************************************************************
+        This function executes SSA on internally stored dataset and returns  basis
+        found by current method.
+           
+        INPUT PARAMETERS:
+            S               -   SSA model
+
+        OUTPUT PARAMETERS:
+            A               -   array[WindowWidth,NBasis],   basis;  vectors  are
+                                stored in matrix columns, by descreasing variance
+            SV              -   array[NBasis]:
+                                * zeros - for model initialized with SSASetAlgoPrecomputed()
+                                * singular values - for other algorithms
+            WindowWidth     -   current window
+            NBasis          -   basis size
+            
+
+        CACHING/REUSE OF THE BASIS
+
+        Caching/reuse of previous results is performed:
+        * first call performs full run of SSA; basis is stored in the cache
+        * subsequent calls reuse previously cached basis
+        * if you call any function which changes model properties (window  length,
+          algorithm, dataset), internal basis will be invalidated.
+        * the only calls which do NOT invalidate basis are listed below:
+          a) ssasetwindow() with same window length
+          b) ssaappendpointandupdate()
+          c) ssaappendsequenceandupdate()
+          d) ssasetalgotopk...() with exactly same K
+          Calling these functions will result in reuse of previously found basis.
+          
+          
+        HANDLING OF DEGENERATE CASES
+            
+        Calling  this  function  in  degenerate  cases  (no  data  or all data are
+        shorter than window size; no algorithm is specified)  returns  basis  with
+        just one zero vector.
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssagetbasis(ssamodel s,
+            ref double[,] a,
+            ref double[] sv,
+            ref int windowwidth,
+            ref int nbasis,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            a = new double[0,0];
+            sv = new double[0];
+            windowwidth = 0;
+            nbasis = 0;
+
+            
+            //
+            // Is it degenerate case?
+            //
+            if( !hassomethingtoanalyze(s, _params) )
+            {
+                windowwidth = s.windowwidth;
+                nbasis = 1;
+                a = new double[windowwidth, 1];
+                for(i=0; i<=windowwidth-1; i++)
+                {
+                    a[i,0] = 0.0;
+                }
+                sv = new double[1];
+                sv[0] = 0.0;
+                return;
+            }
+            
+            //
+            // Update basis.
+            //
+            // It will take care of basis validity flags. AppendLen=0 which means
+            // that we perform initial basis evaluation.
+            //
+            updatebasis(s, 0, 0.0, _params);
+            
+            //
+            // Output
+            //
+            alglib.ap.assert(s.nbasis>0, "SSAGetBasis: integrity check failed");
+            alglib.ap.assert(s.windowwidth>0, "SSAGetBasis: integrity check failed");
+            nbasis = s.nbasis;
+            windowwidth = s.windowwidth;
+            a = new double[windowwidth, nbasis];
+            ablas.rmatrixcopy(windowwidth, nbasis, s.basis, 0, 0, a, 0, 0, _params);
+            sv = new double[nbasis];
+            for(i=0; i<=nbasis-1; i++)
+            {
+                sv[i] = s.sv[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function returns linear recurrence relation (LRR) coefficients  found
+        by current SSA algorithm.
+           
+        INPUT PARAMETERS:
+            S               -   SSA model
+
+        OUTPUT PARAMETERS:
+            A               -   array[WindowWidth-1]. Coefficients  of  the
+                                linear recurrence of the form:
+                                X[W-1] = X[W-2]*A[W-2] + X[W-3]*A[W-3] + ... + X[0]*A[0].
+                                Empty array for WindowWidth=1.
+            WindowWidth     -   current window width
+            
+
+        CACHING/REUSE OF THE BASIS
+
+        Caching/reuse of previous results is performed:
+        * first call performs full run of SSA; basis is stored in the cache
+        * subsequent calls reuse previously cached basis
+        * if you call any function which changes model properties (window  length,
+          algorithm, dataset), internal basis will be invalidated.
+        * the only calls which do NOT invalidate basis are listed below:
+          a) ssasetwindow() with same window length
+          b) ssaappendpointandupdate()
+          c) ssaappendsequenceandupdate()
+          d) ssasetalgotopk...() with exactly same K
+          Calling these functions will result in reuse of previously found basis.
+          
+          
+        HANDLING OF DEGENERATE CASES
+            
+        Calling  this  function  in  degenerate  cases  (no  data  or all data are
+        shorter than window size; no algorithm is specified) returns zeros.
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssagetlrr(ssamodel s,
+            ref double[] a,
+            ref int windowwidth,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            a = new double[0];
+            windowwidth = 0;
+
+            alglib.ap.assert(s.windowwidth>0, "SSAGetLRR: integrity check failed");
+            
+            //
+            // Is it degenerate case?
+            //
+            if( !hassomethingtoanalyze(s, _params) )
+            {
+                windowwidth = s.windowwidth;
+                a = new double[windowwidth-1];
+                for(i=0; i<=windowwidth-2; i++)
+                {
+                    a[i] = 0.0;
+                }
+                return;
+            }
+            
+            //
+            // Update basis.
+            //
+            // It will take care of basis validity flags. AppendLen=0 which means
+            // that we perform initial basis evaluation.
+            //
+            updatebasis(s, 0, 0.0, _params);
+            
+            //
+            // Output
+            //
+            windowwidth = s.windowwidth;
+            a = new double[windowwidth-1];
+            for(i=0; i<=windowwidth-2; i++)
+            {
+                a[i] = s.forecasta[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This  function  executes  SSA  on  internally  stored  dataset and returns
+        analysis  for  the  last  window  of  the  last sequence. Such analysis is
+        an lightweight alternative for full scale reconstruction (see below).
+
+        Typical use case for this function is  real-time  setting,  when  you  are
+        interested in quick-and-dirty (very quick and very  dirty)  processing  of
+        just a few last ticks of the trend.
+
+        IMPORTANT: full  scale  SSA  involves  analysis  of  the  ENTIRE  dataset,
+                   with reconstruction being done for  all  positions  of  sliding
+                   window with subsequent hankelization  (diagonal  averaging)  of
+                   the resulting matrix.
+                   
+                   Such analysis requires O((DataLen-Window)*Window*NBasis)  FLOPs
+                   and can be quite costly. However, it has  nice  noise-canceling
+                   effects due to averaging.
+                   
+                   This function performs REDUCED analysis of the last window.  It
+                   is much faster - just O(Window*NBasis),  but  its  results  are
+                   DIFFERENT from that of ssaanalyzelast(). In  particular,  first
+                   few points of the trend are much more prone to noise.
+           
+        INPUT PARAMETERS:
+            S               -   SSA model
+
+        OUTPUT PARAMETERS:
+            Trend           -   array[WindowSize], reconstructed trend line
+            Noise           -   array[WindowSize], the rest of the signal;
+                                it holds that ActualData = Trend+Noise.
+            NTicks          -   current WindowSize
+            
+
+        CACHING/REUSE OF THE BASIS
+
+        Caching/reuse of previous results is performed:
+        * first call performs full run of SSA; basis is stored in the cache
+        * subsequent calls reuse previously cached basis
+        * if you call any function which changes model properties (window  length,
+          algorithm, dataset), internal basis will be invalidated.
+        * the only calls which do NOT invalidate basis are listed below:
+          a) ssasetwindow() with same window length
+          b) ssaappendpointandupdate()
+          c) ssaappendsequenceandupdate()
+          d) ssasetalgotopk...() with exactly same K
+          Calling these functions will result in reuse of previously found basis.
+
+        In  any  case,  only  basis  is  reused. Reconstruction is performed  from
+        scratch every time you call this function.
+          
+          
+        HANDLING OF DEGENERATE CASES
+
+        Following degenerate cases may happen:
+        * dataset is empty (no analysis can be done)
+        * all sequences are shorter than the window length,no analysis can be done
+        * no algorithm is specified (no analysis can be done)
+        * last sequence is shorter than the window length (analysis can  be  done,
+          but we can not perform reconstruction on the last sequence)
+            
+        Calling this function in degenerate cases returns following result:
+        * in any case, WindowWidth ticks is returned
+        * trend is assumed to be zero
+        * noise is initialized by the last sequence; if last sequence  is  shorter
+          than the window size, it is moved to  the  end  of  the  array, and  the
+          beginning of the noise array is filled by zeros
+          
+        No analysis is performed in degenerate cases (we immediately return  dummy
+        values, no basis is constructed).
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssaanalyzelastwindow(ssamodel s,
+            ref double[] trend,
+            ref double[] noise,
+            ref int nticks,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int offs = 0;
+            int cnt = 0;
+
+            trend = new double[0];
+            noise = new double[0];
+            nticks = 0;
+
+            
+            //
+            // Init
+            //
+            nticks = s.windowwidth;
+            trend = new double[s.windowwidth];
+            noise = new double[s.windowwidth];
+            
+            //
+            // Is it degenerate case?
+            //
+            if( !hassomethingtoanalyze(s, _params) || !issequencebigenough(s, -1, _params) )
+            {
+                for(i=0; i<=nticks-1; i++)
+                {
+                    trend[i] = 0;
+                    noise[i] = 0;
+                }
+                if( s.nsequences>=1 )
+                {
+                    cnt = Math.Min(s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1], nticks);
+                    offs = s.sequenceidx[s.nsequences]-cnt;
+                    for(i=0; i<=cnt-1; i++)
+                    {
+                        noise[nticks-cnt+i] = s.sequencedata[offs+i];
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Update basis.
+            //
+            // It will take care of basis validity flags. AppendLen=0 which means
+            // that we perform initial basis evaluation.
+            //
+            updatebasis(s, 0, 0.0, _params);
+            
+            //
+            // Perform analysis of the last window
+            //
+            alglib.ap.assert(s.sequenceidx[s.nsequences]-s.windowwidth>=0, "SSAAnalyzeLastWindow: integrity check failed");
+            apserv.rvectorsetlengthatleast(ref s.tmp0, s.nbasis, _params);
+            ablas.rmatrixgemv(s.nbasis, s.windowwidth, 1.0, s.basist, 0, 0, 0, s.sequencedata, s.sequenceidx[s.nsequences]-s.windowwidth, 0.0, s.tmp0, 0, _params);
+            ablas.rmatrixgemv(s.windowwidth, s.nbasis, 1.0, s.basis, 0, 0, 0, s.tmp0, 0, 0.0, trend, 0, _params);
+            offs = s.sequenceidx[s.nsequences]-s.windowwidth;
+            cnt = s.windowwidth;
+            for(i=0; i<=cnt-1; i++)
+            {
+                noise[i] = s.sequencedata[offs+i]-trend[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function:
+        * builds SSA basis using internally stored (entire) dataset
+        * returns reconstruction for the last NTicks of the last sequence
+
+        If you want to analyze some other sequence, use ssaanalyzesequence().
+
+        Reconstruction phase involves  generation  of  NTicks-WindowWidth  sliding
+        windows, their decomposition using empirical orthogonal functions found by
+        SSA, followed by averaging of each data point across  several  overlapping
+        windows. Thus, every point in the output trend is reconstructed  using  up
+        to WindowWidth overlapping  windows  (WindowWidth windows exactly  in  the
+        inner points, just one window at the extremal points).
+
+        IMPORTANT: due to averaging this function returns  different  results  for
+                   different values of NTicks. It is expected and not a bug.
+                   
+                   For example:
+                   * Trend[NTicks-1] is always same because it is not averaged  in
+                     any case (same applies to Trend[0]).
+                   * Trend[NTicks-2] has different values  for  NTicks=WindowWidth
+                     and NTicks=WindowWidth+1 because former  case  means that  no
+                     averaging is performed, and latter  case means that averaging
+                     using two sliding windows  is  performed.  Larger  values  of
+                     NTicks produce same results as NTicks=WindowWidth+1.
+                   * ...and so on...
+
+        PERFORMANCE: this  function has O((NTicks-WindowWidth)*WindowWidth*NBasis)
+                     running time. If you work  in  time-constrained  setting  and
+                     have to analyze just a few last ticks, choosing NTicks  equal
+                     to WindowWidth+SmoothingLen, with SmoothingLen=1...WindowWidth
+                     will result in good compromise between noise cancellation and
+                     analysis speed.
+
+        INPUT PARAMETERS:
+            S               -   SSA model
+            NTicks          -   number of ticks to analyze, Nticks>=1.
+                                * special case of NTicks<=WindowWidth  is  handled
+                                  by analyzing last window and  returning   NTicks 
+                                  last ticks.
+                                * special case NTicks>LastSequenceLen  is  handled
+                                  by prepending result with NTicks-LastSequenceLen
+                                  zeros.
+
+        OUTPUT PARAMETERS:
+            Trend           -   array[NTicks], reconstructed trend line
+            Noise           -   array[NTicks], the rest of the signal;
+                                it holds that ActualData = Trend+Noise.
+            
+
+        CACHING/REUSE OF THE BASIS
+
+        Caching/reuse of previous results is performed:
+        * first call performs full run of SSA; basis is stored in the cache
+        * subsequent calls reuse previously cached basis
+        * if you call any function which changes model properties (window  length,
+          algorithm, dataset), internal basis will be invalidated.
+        * the only calls which do NOT invalidate basis are listed below:
+          a) ssasetwindow() with same window length
+          b) ssaappendpointandupdate()
+          c) ssaappendsequenceandupdate()
+          d) ssasetalgotopk...() with exactly same K
+          Calling these functions will result in reuse of previously found basis.
+
+        In  any  case,  only  basis  is  reused. Reconstruction is performed  from
+        scratch every time you call this function.
+          
+          
+        HANDLING OF DEGENERATE CASES
+
+        Following degenerate cases may happen:
+        * dataset is empty (no analysis can be done)
+        * all sequences are shorter than the window length,no analysis can be done
+        * no algorithm is specified (no analysis can be done)
+        * last sequence is shorter than the window length (analysis  can  be done,
+          but we can not perform reconstruction on the last sequence)
+            
+        Calling this function in degenerate cases returns following result:
+        * in any case, NTicks ticks is returned
+        * trend is assumed to be zero
+        * noise is initialized by the last sequence; if last sequence  is  shorter
+          than the window size, it is moved to  the  end  of  the  array, and  the
+          beginning of the noise array is filled by zeros
+          
+        No analysis is performed in degenerate cases (we immediately return  dummy
+        values, no basis is constructed).
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssaanalyzelast(ssamodel s,
+            int nticks,
+            ref double[] trend,
+            ref double[] noise,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int offs = 0;
+            int cnt = 0;
+            int cntzeros = 0;
+
+            trend = new double[0];
+            noise = new double[0];
+
+            alglib.ap.assert(nticks>=1, "SSAAnalyzeLast: NTicks<1");
+            
+            //
+            // Init
+            //
+            trend = new double[nticks];
+            noise = new double[nticks];
+            
+            //
+            // Is it degenerate case?
+            //
+            if( !hassomethingtoanalyze(s, _params) || !issequencebigenough(s, -1, _params) )
+            {
+                for(i=0; i<=nticks-1; i++)
+                {
+                    trend[i] = 0;
+                    noise[i] = 0;
+                }
+                if( s.nsequences>=1 )
+                {
+                    cnt = Math.Min(s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1], nticks);
+                    offs = s.sequenceidx[s.nsequences]-cnt;
+                    for(i=0; i<=cnt-1; i++)
+                    {
+                        noise[nticks-cnt+i] = s.sequencedata[offs+i];
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Fast exit: NTicks<=WindowWidth, just last window is analyzed
+            //
+            if( nticks<=s.windowwidth )
+            {
+                ssaanalyzelastwindow(s, ref s.alongtrend, ref s.alongnoise, ref cnt, _params);
+                offs = s.windowwidth-nticks;
+                for(i=0; i<=nticks-1; i++)
+                {
+                    trend[i] = s.alongtrend[offs+i];
+                    noise[i] = s.alongnoise[offs+i];
+                }
+                return;
+            }
+            
+            //
+            // Update basis.
+            //
+            // It will take care of basis validity flags. AppendLen=0 which means
+            // that we perform initial basis evaluation.
+            //
+            updatebasis(s, 0, 0.0, _params);
+            
+            //
+            // Perform analysis:
+            // * prepend max(NTicks-LastSequenceLength,0) zeros to the beginning
+            //   of array
+            // * analyze the rest with AnalyzeSequence() which assumes that we
+            //   already have basis
+            //
+            alglib.ap.assert(s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]>=s.windowwidth, "SSAAnalyzeLast: integrity check failed / 23vd4");
+            cntzeros = Math.Max(nticks-(s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]), 0);
+            for(i=0; i<=cntzeros-1; i++)
+            {
+                trend[i] = 0.0;
+                noise[i] = 0.0;
+            }
+            cnt = Math.Min(nticks, s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]);
+            analyzesequence(s, s.sequencedata, s.sequenceidx[s.nsequences]-cnt, s.sequenceidx[s.nsequences], trend, noise, cntzeros, _params);
+        }
+
+
+        /*************************************************************************
+        This function:
+        * builds SSA basis using internally stored (entire) dataset
+        * returns reconstruction for the sequence being passed to this function
+
+        If  you  want  to  analyze  last  sequence  stored  in   the   model,  use
+        ssaanalyzelast().
+
+        Reconstruction phase involves  generation  of  NTicks-WindowWidth  sliding
+        windows, their decomposition using empirical orthogonal functions found by
+        SSA, followed by averaging of each data point across  several  overlapping
+        windows. Thus, every point in the output trend is reconstructed  using  up
+        to WindowWidth overlapping  windows  (WindowWidth windows exactly  in  the
+        inner points, just one window at the extremal points).
+
+        PERFORMANCE: this  function has O((NTicks-WindowWidth)*WindowWidth*NBasis)
+                     running time. If you work  in  time-constrained  setting  and
+                     have to analyze just a few last ticks, choosing NTicks  equal
+                     to WindowWidth+SmoothingLen, with SmoothingLen=1...WindowWidth
+                     will result in good compromise between noise cancellation and
+                     analysis speed.
+
+        INPUT PARAMETERS:
+            S               -   SSA model
+            Data            -   array[NTicks], can be larger (only NTicks  leading
+                                elements will be used)
+            NTicks          -   number of ticks to analyze, Nticks>=1.
+                                * special case of NTicks<WindowWidth  is   handled
+                                  by returning zeros as trend, and signal as noise
+
+        OUTPUT PARAMETERS:
+            Trend           -   array[NTicks], reconstructed trend line
+            Noise           -   array[NTicks], the rest of the signal;
+                                it holds that ActualData = Trend+Noise.
+            
+
+        CACHING/REUSE OF THE BASIS
+
+        Caching/reuse of previous results is performed:
+        * first call performs full run of SSA; basis is stored in the cache
+        * subsequent calls reuse previously cached basis
+        * if you call any function which changes model properties (window  length,
+          algorithm, dataset), internal basis will be invalidated.
+        * the only calls which do NOT invalidate basis are listed below:
+          a) ssasetwindow() with same window length
+          b) ssaappendpointandupdate()
+          c) ssaappendsequenceandupdate()
+          d) ssasetalgotopk...() with exactly same K
+          Calling these functions will result in reuse of previously found basis.
+
+        In  any  case,  only  basis  is  reused. Reconstruction is performed  from
+        scratch every time you call this function.
+          
+          
+        HANDLING OF DEGENERATE CASES
+
+        Following degenerate cases may happen:
+        * dataset is empty (no analysis can be done)
+        * all sequences are shorter than the window length,no analysis can be done
+        * no algorithm is specified (no analysis can be done)
+        * sequence being passed is shorter than the window length
+            
+        Calling this function in degenerate cases returns following result:
+        * in any case, NTicks ticks is returned
+        * trend is assumed to be zero
+        * noise is initialized by the sequence.
+          
+        No analysis is performed in degenerate cases (we immediately return  dummy
+        values, no basis is constructed).
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssaanalyzesequence(ssamodel s,
+            double[] data,
+            int nticks,
+            ref double[] trend,
+            ref double[] noise,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            trend = new double[0];
+            noise = new double[0];
+
+            alglib.ap.assert(nticks>=1, "SSAAnalyzeSequence: NTicks<1");
+            alglib.ap.assert(alglib.ap.len(data)>=nticks, "SSAAnalyzeSequence: Data is too short");
+            alglib.ap.assert(apserv.isfinitevector(data, nticks, _params), "SSAAnalyzeSequence: Data contains infinities NANs");
+            
+            //
+            // Init
+            //
+            trend = new double[nticks];
+            noise = new double[nticks];
+            
+            //
+            // Is it degenerate case?
+            //
+            if( !hassomethingtoanalyze(s, _params) || nticks<s.windowwidth )
+            {
+                for(i=0; i<=nticks-1; i++)
+                {
+                    trend[i] = 0;
+                    noise[i] = data[i];
+                }
+                return;
+            }
+            
+            //
+            // Update basis.
+            //
+            // It will take care of basis validity flags. AppendLen=0 which means
+            // that we perform initial basis evaluation.
+            //
+            updatebasis(s, 0, 0.0, _params);
+            
+            //
+            // Perform analysis
+            //
+            analyzesequence(s, data, 0, nticks, trend, noise, 0, _params);
+        }
+
+
+        /*************************************************************************
+        This function builds SSA basis and performs forecasting  for  a  specified
+        number of ticks, returning value of trend.
+
+        Forecast is performed as follows:
+        * SSA  trend  extraction  is  applied  to last WindowWidth elements of the
+          internally stored dataset; this step is basically a noise reduction.
+        * linear recurrence relation is applied to extracted trend
+
+        This function has following running time:
+        * O(NBasis*WindowWidth) for trend extraction phase (always performed)
+        * O(WindowWidth*NTicks) for forecast phase
+
+        NOTE: noise reduction is ALWAYS applied by this algorithm; if you want  to
+              apply recurrence relation  to  raw  unprocessed  data,  use  another
+              function - ssaforecastsequence() which allows to  turn  on  and  off
+              noise reduction phase.
+              
+        NOTE: this algorithm performs prediction using only one - last  -  sliding
+              window.  Predictions  produced   by   such   approach   are   smooth
+              continuations of the reconstructed  trend  line,  but  they  can  be
+              easily corrupted by noise. If you need  noise-resistant  prediction,
+              use ssaforecastavglast() function, which averages predictions  built
+              using several sliding windows.
+
+        INPUT PARAMETERS:
+            S               -   SSA model
+            NTicks          -   number of ticks to forecast, NTicks>=1
+
+        OUTPUT PARAMETERS:
+            Trend           -   array[NTicks], predicted trend line
+            
+
+        CACHING/REUSE OF THE BASIS
+
+        Caching/reuse of previous results is performed:
+        * first call performs full run of SSA; basis is stored in the cache
+        * subsequent calls reuse previously cached basis
+        * if you call any function which changes model properties (window  length,
+          algorithm, dataset), internal basis will be invalidated.
+        * the only calls which do NOT invalidate basis are listed below:
+          a) ssasetwindow() with same window length
+          b) ssaappendpointandupdate()
+          c) ssaappendsequenceandupdate()
+          d) ssasetalgotopk...() with exactly same K
+          Calling these functions will result in reuse of previously found basis.
+
+
+        HANDLING OF DEGENERATE CASES
+
+        Following degenerate cases may happen:
+        * dataset is empty (no analysis can be done)
+        * all sequences are shorter than the window length,no analysis can be done
+        * no algorithm is specified (no analysis can be done)
+        * last sequence is shorter than the WindowWidth   (analysis  can  be done,
+          but we can not perform forecasting on the last sequence)
+        * window lentgh is 1 (impossible to use for forecasting)
+        * SSA analysis algorithm is  configured  to  extract  basis  whose size is
+          equal to window length (impossible to use for  forecasting;  only  basis
+          whose size is less than window length can be used).
+            
+        Calling this function in degenerate cases returns following result:
+        * NTicks  copies  of  the  last  value is returned for non-empty task with
+          large enough dataset, but with overcomplete  basis  (window  width=1  or
+          basis size is equal to window width)
+        * zero trend with length=NTicks is returned for empty task
+          
+        No analysis is performed in degenerate cases (we immediately return  dummy
+        values, no basis is ever constructed).
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssaforecastlast(ssamodel s,
+            int nticks,
+            ref double[] trend,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int winw = 0;
+
+            trend = new double[0];
+
+            alglib.ap.assert(nticks>=1, "SSAForecast: NTicks<1");
+            
+            //
+            // Init
+            //
+            winw = s.windowwidth;
+            trend = new double[nticks];
+            
+            //
+            // Is it degenerate case?
+            //
+            if( !hassomethingtoanalyze(s, _params) )
+            {
+                for(i=0; i<=nticks-1; i++)
+                {
+                    trend[i] = 0;
+                }
+                return;
+            }
+            alglib.ap.assert(s.nsequences>0, "SSAForecastLast: integrity check failed");
+            if( s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]<winw )
+            {
+                for(i=0; i<=nticks-1; i++)
+                {
+                    trend[i] = 0;
+                }
+                return;
+            }
+            if( winw==1 )
+            {
+                alglib.ap.assert(s.nsequences>0, "SSAForecast: integrity check failed / 2355");
+                alglib.ap.assert(s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]>0, "SSAForecast: integrity check failed");
+                for(i=0; i<=nticks-1; i++)
+                {
+                    trend[i] = s.sequencedata[s.sequenceidx[s.nsequences]-1];
+                }
+                return;
+            }
+            
+            //
+            // Update basis and recurrent relation.
+            //
+            // It will take care of basis validity flags. AppendLen=0 which means
+            // that we perform initial basis evaluation.
+            //
+            updatebasis(s, 0, 0.0, _params);
+            alglib.ap.assert(s.nbasis<=winw && s.nbasis>0, "SSAForecast: integrity check failed / 4f5et");
+            if( s.nbasis==winw )
+            {
+                
+                //
+                // Handle degenerate situation with basis whose size
+                // is equal to window length.
+                //
+                alglib.ap.assert(s.nsequences>0, "SSAForecast: integrity check failed / 2355");
+                alglib.ap.assert(s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]>0, "SSAForecast: integrity check failed");
+                for(i=0; i<=nticks-1; i++)
+                {
+                    trend[i] = s.sequencedata[s.sequenceidx[s.nsequences]-1];
+                }
+                return;
+            }
+            
+            //
+            // Apply recurrent formula for SSA forecasting:
+            // * first, perform smoothing of the last window
+            // * second, perform analysis phase
+            //
+            alglib.ap.assert(s.nsequences>0, "SSAForecastLast: integrity check failed");
+            alglib.ap.assert(s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]>=s.windowwidth, "SSAForecastLast: integrity check failed");
+            apserv.rvectorsetlengthatleast(ref s.tmp0, s.nbasis, _params);
+            apserv.rvectorsetlengthatleast(ref s.fctrend, s.windowwidth, _params);
+            ablas.rmatrixgemv(s.nbasis, s.windowwidth, 1.0, s.basist, 0, 0, 0, s.sequencedata, s.sequenceidx[s.nsequences]-s.windowwidth, 0.0, s.tmp0, 0, _params);
+            ablas.rmatrixgemv(s.windowwidth, s.nbasis, 1.0, s.basis, 0, 0, 0, s.tmp0, 0, 0.0, s.fctrend, 0, _params);
+            apserv.rvectorsetlengthatleast(ref s.tmp1, winw-1, _params);
+            for(i=1; i<=winw-1; i++)
+            {
+                s.tmp1[i-1] = s.fctrend[i];
+            }
+            for(i=0; i<=nticks-1; i++)
+            {
+                v = s.forecasta[0]*s.tmp1[0];
+                for(j=1; j<=winw-2; j++)
+                {
+                    v = v+s.forecasta[j]*s.tmp1[j];
+                    s.tmp1[j-1] = s.tmp1[j];
+                }
+                trend[i] = v;
+                s.tmp1[winw-2] = v;
+            }
+        }
+
+
+        /*************************************************************************
+        This function builds SSA  basis  and  performs  forecasting  for  a  user-
+        specified sequence, returning value of trend.
+
+        Forecasting is done in two stages:
+        * first,  we  extract  trend  from the WindowWidth  last  elements of  the
+          sequence. This stage is optional, you  can  turn  it  off  if  you  pass
+          data which are already processed with SSA. Of course, you  can  turn  it
+          off even for raw data, but it is not recommended - noise suppression  is
+          very important for correct prediction.
+        * then, we apply LRR for last  WindowWidth-1  elements  of  the  extracted
+          trend.
+
+        This function has following running time:
+        * O(NBasis*WindowWidth) for trend extraction phase
+        * O(WindowWidth*NTicks) for forecast phase
+              
+        NOTE: this algorithm performs prediction using only one - last  -  sliding
+              window.  Predictions  produced   by   such   approach   are   smooth
+              continuations of the reconstructed  trend  line,  but  they  can  be
+              easily corrupted by noise. If you need  noise-resistant  prediction,
+              use ssaforecastavgsequence() function,  which  averages  predictions
+              built using several sliding windows.
+
+        INPUT PARAMETERS:
+            S               -   SSA model
+            Data            -   array[NTicks], data to forecast
+            DataLen         -   number of ticks in the data, DataLen>=1
+            ForecastLen     -   number of ticks to predict, ForecastLen>=1
+            ApplySmoothing  -   whether to apply smoothing trend extraction or not;
+                                if you do not know what to specify, pass True.
+
+        OUTPUT PARAMETERS:
+            Trend           -   array[ForecastLen], forecasted trend
+            
+
+        CACHING/REUSE OF THE BASIS
+
+        Caching/reuse of previous results is performed:
+        * first call performs full run of SSA; basis is stored in the cache
+        * subsequent calls reuse previously cached basis
+        * if you call any function which changes model properties (window  length,
+          algorithm, dataset), internal basis will be invalidated.
+        * the only calls which do NOT invalidate basis are listed below:
+          a) ssasetwindow() with same window length
+          b) ssaappendpointandupdate()
+          c) ssaappendsequenceandupdate()
+          d) ssasetalgotopk...() with exactly same K
+          Calling these functions will result in reuse of previously found basis.
+
+
+        HANDLING OF DEGENERATE CASES
+
+        Following degenerate cases may happen:
+        * dataset is empty (no analysis can be done)
+        * all sequences are shorter than the window length,no analysis can be done
+        * no algorithm is specified (no analysis can be done)
+        * data sequence is shorter than the WindowWidth   (analysis  can  be done,
+          but we can not perform forecasting on the last sequence)
+        * window lentgh is 1 (impossible to use for forecasting)
+        * SSA analysis algorithm is  configured  to  extract  basis  whose size is
+          equal to window length (impossible to use for  forecasting;  only  basis
+          whose size is less than window length can be used).
+            
+        Calling this function in degenerate cases returns following result:
+        * ForecastLen copies of the last value is returned for non-empty task with
+          large enough dataset, but with overcomplete  basis  (window  width=1  or
+          basis size is equal to window width)
+        * zero trend with length=ForecastLen is returned for empty task
+          
+        No analysis is performed in degenerate cases (we immediately return  dummy
+        values, no basis is ever constructed).
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssaforecastsequence(ssamodel s,
+            double[] data,
+            int datalen,
+            int forecastlen,
+            bool applysmoothing,
+            ref double[] trend,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int winw = 0;
+
+            trend = new double[0];
+
+            alglib.ap.assert(datalen>=1, "SSAForecastSequence: DataLen<1");
+            alglib.ap.assert(alglib.ap.len(data)>=datalen, "SSAForecastSequence: Data is too short");
+            alglib.ap.assert(apserv.isfinitevector(data, datalen, _params), "SSAForecastSequence: Data contains infinities NANs");
+            alglib.ap.assert(forecastlen>=1, "SSAForecastSequence: ForecastLen<1");
+            
+            //
+            // Init
+            //
+            winw = s.windowwidth;
+            trend = new double[forecastlen];
+            
+            //
+            // Is it degenerate case?
+            //
+            if( !hassomethingtoanalyze(s, _params) || datalen<winw )
+            {
+                for(i=0; i<=forecastlen-1; i++)
+                {
+                    trend[i] = 0;
+                }
+                return;
+            }
+            if( winw==1 )
+            {
+                for(i=0; i<=forecastlen-1; i++)
+                {
+                    trend[i] = data[datalen-1];
+                }
+                return;
+            }
+            
+            //
+            // Update basis.
+            //
+            // It will take care of basis validity flags. AppendLen=0 which means
+            // that we perform initial basis evaluation.
+            //
+            updatebasis(s, 0, 0.0, _params);
+            alglib.ap.assert(s.nbasis<=winw && s.nbasis>0, "SSAForecast: integrity check failed / 4f5et");
+            if( s.nbasis==winw )
+            {
+                
+                //
+                // Handle degenerate situation with basis whose size
+                // is equal to window length.
+                //
+                for(i=0; i<=forecastlen-1; i++)
+                {
+                    trend[i] = data[datalen-1];
+                }
+                return;
+            }
+            
+            //
+            // Perform trend extraction
+            //
+            apserv.rvectorsetlengthatleast(ref s.fctrend, s.windowwidth, _params);
+            if( applysmoothing )
+            {
+                alglib.ap.assert(datalen>=winw, "SSAForecastSequence: integrity check failed");
+                apserv.rvectorsetlengthatleast(ref s.tmp0, s.nbasis, _params);
+                ablas.rmatrixgemv(s.nbasis, winw, 1.0, s.basist, 0, 0, 0, data, datalen-winw, 0.0, s.tmp0, 0, _params);
+                ablas.rmatrixgemv(winw, s.nbasis, 1.0, s.basis, 0, 0, 0, s.tmp0, 0, 0.0, s.fctrend, 0, _params);
+            }
+            else
+            {
+                for(i=0; i<=winw-1; i++)
+                {
+                    s.fctrend[i] = data[datalen+i-winw];
+                }
+            }
+            
+            //
+            // Apply recurrent formula for SSA forecasting
+            //
+            apserv.rvectorsetlengthatleast(ref s.tmp1, winw-1, _params);
+            for(i=1; i<=winw-1; i++)
+            {
+                s.tmp1[i-1] = s.fctrend[i];
+            }
+            for(i=0; i<=forecastlen-1; i++)
+            {
+                v = s.forecasta[0]*s.tmp1[0];
+                for(j=1; j<=winw-2; j++)
+                {
+                    v = v+s.forecasta[j]*s.tmp1[j];
+                    s.tmp1[j-1] = s.tmp1[j];
+                }
+                trend[i] = v;
+                s.tmp1[winw-2] = v;
+            }
+        }
+
+
+        /*************************************************************************
+        This function builds SSA basis and performs forecasting  for  a  specified
+        number of ticks, returning value of trend.
+
+        Forecast is performed as follows:
+        * SSA  trend  extraction  is  applied to last  M  sliding windows  of  the
+          internally stored dataset
+        * for each of M sliding windows, M predictions are built
+        * average value of M predictions is returned
+
+        This function has following running time:
+        * O(NBasis*WindowWidth*M) for trend extraction phase (always performed)
+        * O(WindowWidth*NTicks*M) for forecast phase
+
+        NOTE: noise reduction is ALWAYS applied by this algorithm; if you want  to
+              apply recurrence relation  to  raw  unprocessed  data,  use  another
+              function - ssaforecastsequence() which allows to  turn  on  and  off
+              noise reduction phase.
+              
+        NOTE: combination of several predictions results in lesser sensitivity  to
+              noise, but it may produce undesirable discontinuities  between  last
+              point of the trend and first point of the prediction. The reason  is
+              that  last  point  of  the  trend is usually corrupted by noise, but
+              average  value of  several  predictions  is less sensitive to noise,
+              thus discontinuity appears. It is not a bug.
+
+        INPUT PARAMETERS:
+            S               -   SSA model
+            M               -   number  of  sliding  windows  to combine, M>=1. If
+                                your dataset has less than M sliding windows, this
+                                parameter will be silently reduced.
+            NTicks          -   number of ticks to forecast, NTicks>=1
+
+        OUTPUT PARAMETERS:
+            Trend           -   array[NTicks], predicted trend line
+            
+
+        CACHING/REUSE OF THE BASIS
+
+        Caching/reuse of previous results is performed:
+        * first call performs full run of SSA; basis is stored in the cache
+        * subsequent calls reuse previously cached basis
+        * if you call any function which changes model properties (window  length,
+          algorithm, dataset), internal basis will be invalidated.
+        * the only calls which do NOT invalidate basis are listed below:
+          a) ssasetwindow() with same window length
+          b) ssaappendpointandupdate()
+          c) ssaappendsequenceandupdate()
+          d) ssasetalgotopk...() with exactly same K
+          Calling these functions will result in reuse of previously found basis.
+
+
+        HANDLING OF DEGENERATE CASES
+
+        Following degenerate cases may happen:
+        * dataset is empty (no analysis can be done)
+        * all sequences are shorter than the window length,no analysis can be done
+        * no algorithm is specified (no analysis can be done)
+        * last sequence is shorter than the WindowWidth   (analysis  can  be done,
+          but we can not perform forecasting on the last sequence)
+        * window lentgh is 1 (impossible to use for forecasting)
+        * SSA analysis algorithm is  configured  to  extract  basis  whose size is
+          equal to window length (impossible to use for  forecasting;  only  basis
+          whose size is less than window length can be used).
+            
+        Calling this function in degenerate cases returns following result:
+        * NTicks  copies  of  the  last  value is returned for non-empty task with
+          large enough dataset, but with overcomplete  basis  (window  width=1  or
+          basis size is equal to window width)
+        * zero trend with length=NTicks is returned for empty task
+          
+        No analysis is performed in degenerate cases (we immediately return  dummy
+        values, no basis is ever constructed).
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssaforecastavglast(ssamodel s,
+            int m,
+            int nticks,
+            ref double[] trend,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int winw = 0;
+
+            trend = new double[0];
+
+            alglib.ap.assert(nticks>=1, "SSAForecastAvgLast: NTicks<1");
+            alglib.ap.assert(m>=1, "SSAForecastAvgLast: M<1");
+            
+            //
+            // Init
+            //
+            winw = s.windowwidth;
+            trend = new double[nticks];
+            
+            //
+            // Is it degenerate case?
+            //
+            if( !hassomethingtoanalyze(s, _params) )
+            {
+                for(i=0; i<=nticks-1; i++)
+                {
+                    trend[i] = 0;
+                }
+                return;
+            }
+            alglib.ap.assert(s.nsequences>0, "SSAForecastAvgLast: integrity check failed");
+            if( s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]<winw )
+            {
+                for(i=0; i<=nticks-1; i++)
+                {
+                    trend[i] = 0;
+                }
+                return;
+            }
+            if( winw==1 )
+            {
+                alglib.ap.assert(s.nsequences>0, "SSAForecastAvgLast: integrity check failed / 2355");
+                alglib.ap.assert(s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]>0, "SSAForecastAvgLast: integrity check failed");
+                for(i=0; i<=nticks-1; i++)
+                {
+                    trend[i] = s.sequencedata[s.sequenceidx[s.nsequences]-1];
+                }
+                return;
+            }
+            
+            //
+            // Update basis and recurrent relation.
+            //
+            // It will take care of basis validity flags. AppendLen=0 which means
+            // that we perform initial basis evaluation.
+            //
+            updatebasis(s, 0, 0.0, _params);
+            alglib.ap.assert(s.nbasis<=winw && s.nbasis>0, "SSAForecastAvgLast: integrity check failed / 4f5et");
+            if( s.nbasis==winw )
+            {
+                
+                //
+                // Handle degenerate situation with basis whose size
+                // is equal to window length.
+                //
+                alglib.ap.assert(s.nsequences>0, "SSAForecastAvgLast: integrity check failed / 2355");
+                alglib.ap.assert(s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]>0, "SSAForecastAvgLast: integrity check failed");
+                for(i=0; i<=nticks-1; i++)
+                {
+                    trend[i] = s.sequencedata[s.sequenceidx[s.nsequences]-1];
+                }
+                return;
+            }
+            
+            //
+            // Decrease M if we have less than M sliding windows.
+            // Forecast.
+            //
+            m = Math.Min(m, s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]-winw+1);
+            alglib.ap.assert(m>=1, "SSAForecastAvgLast: integrity check failed");
+            forecastavgsequence(s, s.sequencedata, s.sequenceidx[s.nsequences-1], s.sequenceidx[s.nsequences], m, nticks, true, trend, 0, _params);
+        }
+
+
+        /*************************************************************************
+        This function builds SSA  basis  and  performs  forecasting  for  a  user-
+        specified sequence, returning value of trend.
+
+        Forecasting is done in two stages:
+        * first,  we  extract  trend  from M last sliding windows of the sequence.
+          This stage is optional, you can  turn  it  off  if  you  pass data which
+          are already processed with SSA. Of course, you  can  turn  it  off  even
+          for raw data, but it is not recommended  -  noise  suppression  is  very
+          important for correct prediction.
+        * then, we apply LRR independently for M sliding windows
+        * average of M predictions is returned
+
+        This function has following running time:
+        * O(NBasis*WindowWidth*M) for trend extraction phase
+        * O(WindowWidth*NTicks*M) for forecast phase
+              
+        NOTE: combination of several predictions results in lesser sensitivity  to
+              noise, but it may produce undesirable discontinuities  between  last
+              point of the trend and first point of the prediction. The reason  is
+              that  last  point  of  the  trend is usually corrupted by noise, but
+              average  value of  several  predictions  is less sensitive to noise,
+              thus discontinuity appears. It is not a bug.
+
+        INPUT PARAMETERS:
+            S               -   SSA model
+            Data            -   array[NTicks], data to forecast
+            DataLen         -   number of ticks in the data, DataLen>=1
+            M               -   number  of  sliding  windows  to combine, M>=1. If
+                                your dataset has less than M sliding windows, this
+                                parameter will be silently reduced.
+            ForecastLen     -   number of ticks to predict, ForecastLen>=1
+            ApplySmoothing  -   whether to apply smoothing trend extraction or not.
+                                if you do not know what to specify, pass true.
+
+        OUTPUT PARAMETERS:
+            Trend           -   array[ForecastLen], forecasted trend
+            
+
+        CACHING/REUSE OF THE BASIS
+
+        Caching/reuse of previous results is performed:
+        * first call performs full run of SSA; basis is stored in the cache
+        * subsequent calls reuse previously cached basis
+        * if you call any function which changes model properties (window  length,
+          algorithm, dataset), internal basis will be invalidated.
+        * the only calls which do NOT invalidate basis are listed below:
+          a) ssasetwindow() with same window length
+          b) ssaappendpointandupdate()
+          c) ssaappendsequenceandupdate()
+          d) ssasetalgotopk...() with exactly same K
+          Calling these functions will result in reuse of previously found basis.
+
+
+        HANDLING OF DEGENERATE CASES
+
+        Following degenerate cases may happen:
+        * dataset is empty (no analysis can be done)
+        * all sequences are shorter than the window length,no analysis can be done
+        * no algorithm is specified (no analysis can be done)
+        * data sequence is shorter than the WindowWidth   (analysis  can  be done,
+          but we can not perform forecasting on the last sequence)
+        * window lentgh is 1 (impossible to use for forecasting)
+        * SSA analysis algorithm is  configured  to  extract  basis  whose size is
+          equal to window length (impossible to use for  forecasting;  only  basis
+          whose size is less than window length can be used).
+            
+        Calling this function in degenerate cases returns following result:
+        * ForecastLen copies of the last value is returned for non-empty task with
+          large enough dataset, but with overcomplete  basis  (window  width=1  or
+          basis size is equal to window width)
+        * zero trend with length=ForecastLen is returned for empty task
+          
+        No analysis is performed in degenerate cases (we immediately return  dummy
+        values, no basis is ever constructed).
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void ssaforecastavgsequence(ssamodel s,
+            double[] data,
+            int datalen,
+            int m,
+            int forecastlen,
+            bool applysmoothing,
+            ref double[] trend,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int winw = 0;
+
+            trend = new double[0];
+
+            alglib.ap.assert(datalen>=1, "SSAForecastAvgSequence: DataLen<1");
+            alglib.ap.assert(m>=1, "SSAForecastAvgSequence: M<1");
+            alglib.ap.assert(alglib.ap.len(data)>=datalen, "SSAForecastAvgSequence: Data is too short");
+            alglib.ap.assert(apserv.isfinitevector(data, datalen, _params), "SSAForecastAvgSequence: Data contains infinities NANs");
+            alglib.ap.assert(forecastlen>=1, "SSAForecastAvgSequence: ForecastLen<1");
+            
+            //
+            // Init
+            //
+            winw = s.windowwidth;
+            trend = new double[forecastlen];
+            
+            //
+            // Is it degenerate case?
+            //
+            if( !hassomethingtoanalyze(s, _params) || datalen<winw )
+            {
+                for(i=0; i<=forecastlen-1; i++)
+                {
+                    trend[i] = 0;
+                }
+                return;
+            }
+            if( winw==1 )
+            {
+                for(i=0; i<=forecastlen-1; i++)
+                {
+                    trend[i] = data[datalen-1];
+                }
+                return;
+            }
+            
+            //
+            // Update basis.
+            //
+            // It will take care of basis validity flags. AppendLen=0 which means
+            // that we perform initial basis evaluation.
+            //
+            updatebasis(s, 0, 0.0, _params);
+            alglib.ap.assert(s.nbasis<=winw && s.nbasis>0, "SSAForecast: integrity check failed / 4f5et");
+            if( s.nbasis==winw )
+            {
+                
+                //
+                // Handle degenerate situation with basis whose size
+                // is equal to window length.
+                //
+                for(i=0; i<=forecastlen-1; i++)
+                {
+                    trend[i] = data[datalen-1];
+                }
+                return;
+            }
+            
+            //
+            // Decrease M if we have less than M sliding windows.
+            // Forecast.
+            //
+            m = Math.Min(m, datalen-winw+1);
+            alglib.ap.assert(m>=1, "SSAForecastAvgLast: integrity check failed");
+            forecastavgsequence(s, data, 0, datalen, m, forecastlen, applysmoothing, trend, 0, _params);
+        }
+
+
+        /*************************************************************************
+        This function evaluates current model and tells whether we have some  data
+        which can be analyzed by current algorithm, or not.
+
+        No analysis can be done in the following degenerate cases:
+        * dataset is empty
+        * all sequences are shorter than the window length
+        * no algorithm is specified
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool hassomethingtoanalyze(ssamodel s,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+            bool allsmaller = new bool();
+            bool isdegenerate = new bool();
+
+            isdegenerate = false;
+            isdegenerate = isdegenerate || s.algotype==0;
+            isdegenerate = isdegenerate || s.nsequences==0;
+            allsmaller = true;
+            for(i=0; i<=s.nsequences-1; i++)
+            {
+                allsmaller = allsmaller && s.sequenceidx[i+1]-s.sequenceidx[i]<s.windowwidth;
+            }
+            isdegenerate = isdegenerate || allsmaller;
+            result = !isdegenerate;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function checks whether I-th sequence is big enough for analysis or not.
+
+        I=-1 is used to denote last sequence (for NSequences=0)
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool issequencebigenough(ssamodel s,
+            int i,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            alglib.ap.assert(i>=-1 && i<s.nsequences);
+            result = false;
+            if( s.nsequences==0 )
+            {
+                return result;
+            }
+            if( i<0 )
+            {
+                i = s.nsequences-1;
+            }
+            result = s.sequenceidx[i+1]-s.sequenceidx[i]>=s.windowwidth;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function performs basis update. Either full update (recalculated from
+        the very beginning) or partial update (handles append to the  end  of  the
+        dataset).
+
+        With AppendLen=0 this function behaves as follows:
+        * if AreBasisAndSolverValid=False, then  solver  object  is  created  from
+          scratch, initial calculations are performed according  to  specific  SSA
+          algorithm being chosen. Basis/Solver validity flag is set to True,  then
+          we immediately return.
+        * if AreBasisAndSolverValid=True, then nothing is done  -  we  immediately
+          return.
+
+        With AppendLen>0 this function behaves as follows:
+        * if AreBasisAndSolverValid=False, then exception is  generated;  you  can
+          append points only to fully constructed basis. Call this  function  with
+          zero AppendLen BEFORE append, then perform append, then call it one more
+          time with non-zero AppendLen.
+        * if AreBasisAndSolverValid=True, then basis is incrementally updated.  It
+          also updates recurrence relation used for prediction. It is expected that
+          either AppendLen=1, or AppendLen=length(last_sequence). Basis update  is
+          performed with probability UpdateIts (larger-than-one values  mean  that
+          some amount of iterations is always performed).
+
+
+        In any case, after calling this function we either:
+        * have an exception
+        * have completely valid basis
+
+        IMPORTANT: this function expects that we do NOT call it for degenerate tasks
+                   (no data). So, call it after check with HasSomethingToAnalyze()
+                   returned True.
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void updatebasis(ssamodel s,
+            int appendlen,
+            double updateits,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int srcoffs = 0;
+            int dstoffs = 0;
+            int winw = 0;
+            int windowstotal = 0;
+            int requesttype = 0;
+            int requestsize = 0;
+            double v = 0;
+            bool degeneraterecurrence = new bool();
+            double nu2 = 0;
+            int subspaceits = 0;
+            bool needevd = new bool();
+
+            winw = s.windowwidth;
+            
+            //
+            // Critical checks
+            //
+            alglib.ap.assert(appendlen>=0, "SSA: incorrect parameters passed to UpdateBasis(), integrity check failed");
+            alglib.ap.assert(!(!s.arebasisandsolvervalid && appendlen!=0), "SSA: incorrect parameters passed to UpdateBasis(), integrity check failed");
+            alglib.ap.assert(!(appendlen==0 && (double)(updateits)>(double)(0.0)), "SSA: incorrect parameters passed to UpdateBasis(), integrity check failed");
+            
+            //
+            // Everything is OK, nothing to do
+            //
+            if( s.arebasisandsolvervalid && appendlen==0 )
+            {
+                return;
+            }
+            
+            //
+            // Seed RNG with fixed or random seed.
+            //
+            // RNG used when pseudorandomly deciding whether
+            // to re-evaluate basis or not. Sandom seed is
+            // important when we have several simultaneously
+            // calculated SSA models - we do not want them
+            // to be re-evaluated in same moments).
+            //
+            if( !s.arebasisandsolvervalid )
+            {
+                if( s.rngseed>0 )
+                {
+                    hqrnd.hqrndseed(s.rngseed, s.rngseed+235, s.rs, _params);
+                }
+                else
+                {
+                    hqrnd.hqrndrandomize(s.rs, _params);
+                }
+            }
+            
+            //
+            // Compute XXT for algorithms which need it
+            //
+            if( !s.arebasisandsolvervalid )
+            {
+                alglib.ap.assert(appendlen==0, "SSA: integrity check failed / 34cx6");
+                if( s.algotype==2 )
+                {
+                    
+                    //
+                    // Compute X*X^T for direct algorithm.
+                    // Quite straightforward, no subtle optimizations.
+                    //
+                    apserv.rmatrixsetlengthatleast(ref s.xxt, winw, winw, _params);
+                    windowstotal = 0;
+                    for(i=0; i<=s.nsequences-1; i++)
+                    {
+                        windowstotal = windowstotal+Math.Max(s.sequenceidx[i+1]-s.sequenceidx[i]-winw+1, 0);
+                    }
+                    alglib.ap.assert(windowstotal>0, "SSA: integrity check in UpdateBasis() failed / 76t34");
+                    for(i=0; i<=winw-1; i++)
+                    {
+                        for(j=0; j<=winw-1; j++)
+                        {
+                            s.xxt[i,j] = 0;
+                        }
+                    }
+                    updatexxtprepare(s, windowstotal, winw, s.memorylimit, _params);
+                    for(i=0; i<=s.nsequences-1; i++)
+                    {
+                        for(j=0; j<=Math.Max(s.sequenceidx[i+1]-s.sequenceidx[i]-winw+1, 0)-1; j++)
+                        {
+                            updatexxtsend(s, s.sequencedata, s.sequenceidx[i]+j, s.xxt, _params);
+                        }
+                    }
+                    updatexxtfinalize(s, s.xxt, _params);
+                }
+                if( s.algotype==3 )
+                {
+                    
+                    //
+                    // Compute X*X^T for real-time algorithm:
+                    // * prepare queue of windows to merge into XXT
+                    // * shuffle queue in order to avoid time-related biases in algorithm
+                    // * dequeue first chunk
+                    //
+                    apserv.rmatrixsetlengthatleast(ref s.xxt, winw, winw, _params);
+                    windowstotal = 0;
+                    for(i=0; i<=s.nsequences-1; i++)
+                    {
+                        windowstotal = windowstotal+Math.Max(s.sequenceidx[i+1]-s.sequenceidx[i]-winw+1, 0);
+                    }
+                    alglib.ap.assert(windowstotal>0, "SSA: integrity check in UpdateBasis() failed / 76t34");
+                    apserv.ivectorsetlengthatleast(ref s.rtqueue, windowstotal, _params);
+                    dstoffs = 0;
+                    for(i=0; i<=s.nsequences-1; i++)
+                    {
+                        for(j=0; j<=Math.Max(s.sequenceidx[i+1]-s.sequenceidx[i]-winw+1, 0)-1; j++)
+                        {
+                            srcoffs = s.sequenceidx[i]+j;
+                            s.rtqueue[dstoffs] = srcoffs;
+                            apserv.inc(ref dstoffs, _params);
+                        }
+                    }
+                    alglib.ap.assert(dstoffs==windowstotal, "SSA: integrity check in UpdateBasis() failed / fh45f");
+                    if( s.rtpowerup>1 )
+                    {
+                        
+                        //
+                        // Shuffle queue, it helps to avoid time-related bias in algorithm
+                        //
+                        for(i=0; i<=windowstotal-1; i++)
+                        {
+                            j = i+hqrnd.hqrnduniformi(s.rs, windowstotal-i, _params);
+                            apserv.swapelementsi(s.rtqueue, i, j, _params);
+                        }
+                    }
+                    s.rtqueuecnt = windowstotal;
+                    s.rtqueuechunk = 1;
+                    s.rtqueuechunk = Math.Max(s.rtqueuechunk, s.rtqueuecnt/s.rtpowerup);
+                    s.rtqueuechunk = Math.Max(s.rtqueuechunk, 2*s.topk);
+                    realtimedequeue(s, 0.0, Math.Min(s.rtqueuechunk, s.rtqueuecnt), _params);
+                }
+            }
+            
+            //
+            // Handle possible updates for XXT:
+            // * check that append involves either last point of last sequence,
+            //   or entire last sequence
+            // * if last sequence is shorter than window width, perform quick exit -
+            //   we have nothing to update - no windows to insert into XXT
+            // * update XXT
+            //
+            if( appendlen>0 )
+            {
+                alglib.ap.assert(s.arebasisandsolvervalid, "SSA: integrity check failed / 5gvz3");
+                alglib.ap.assert(s.nsequences>=1, "SSA: integrity check failed / 658ev");
+                alglib.ap.assert(appendlen==1 || appendlen==s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]-winw+1, "SSA: integrity check failed / sd3g7");
+                if( s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]<winw )
+                {
+                    
+                    //
+                    // Last sequence is too short, nothing to update
+                    //
+                    return;
+                }
+                if( s.algotype==2 || s.algotype==3 )
+                {
+                    if( appendlen>1 )
+                    {
+                        
+                        //
+                        // Long append, use GEMM for updates
+                        //
+                        updatexxtprepare(s, appendlen, winw, s.memorylimit, _params);
+                        for(j=0; j<=Math.Max(s.sequenceidx[s.nsequences]-s.sequenceidx[s.nsequences-1]-winw+1, 0)-1; j++)
+                        {
+                            updatexxtsend(s, s.sequencedata, s.sequenceidx[s.nsequences-1]+j, s.xxt, _params);
+                        }
+                        updatexxtfinalize(s, s.xxt, _params);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Just one element is added, use rank-1 update
+                        //
+                        ablas.rmatrixger(winw, winw, s.xxt, 0, 0, 1.0, s.sequencedata, s.sequenceidx[s.nsequences]-winw, s.sequencedata, s.sequenceidx[s.nsequences]-winw, _params);
+                    }
+                }
+            }
+            
+            //
+            // Now, perform basis calculation - either full recalculation (AppendLen=0)
+            // or quick update (AppendLen>0).
+            //
+            if( s.algotype==1 )
+            {
+                
+                //
+                // Precomputed basis
+                //
+                if( winw!=s.precomputedwidth )
+                {
+                    
+                    //
+                    // Window width has changed, reset basis to zeros
+                    //
+                    s.nbasis = 1;
+                    apserv.rmatrixsetlengthatleast(ref s.basis, winw, 1, _params);
+                    apserv.rvectorsetlengthatleast(ref s.sv, 1, _params);
+                    for(i=0; i<=winw-1; i++)
+                    {
+                        s.basis[i,0] = 0.0;
+                    }
+                    s.sv[0] = 0.0;
+                }
+                else
+                {
+                    
+                    //
+                    // OK, use precomputed basis
+                    //
+                    s.nbasis = s.precomputednbasis;
+                    apserv.rmatrixsetlengthatleast(ref s.basis, winw, s.nbasis, _params);
+                    apserv.rvectorsetlengthatleast(ref s.sv, s.nbasis, _params);
+                    for(j=0; j<=s.nbasis-1; j++)
+                    {
+                        s.sv[j] = 0.0;
+                        for(i=0; i<=winw-1; i++)
+                        {
+                            s.basis[i,j] = s.precomputedbasis[i,j];
+                        }
+                    }
+                }
+                apserv.rmatrixsetlengthatleast(ref s.basist, s.nbasis, winw, _params);
+                ablas.rmatrixtranspose(winw, s.nbasis, s.basis, 0, 0, s.basist, 0, 0, _params);
+            }
+            else
+            {
+                if( s.algotype==2 )
+                {
+                    
+                    //
+                    // Direct top-K algorithm
+                    //
+                    // Calculate eigenvectors with SMatrixEVD(), reorder by descending
+                    // of magnitudes.
+                    //
+                    // Update is performed for invalid basis or for non-zero UpdateIts.
+                    //
+                    needevd = !s.arebasisandsolvervalid;
+                    needevd = needevd || (double)(updateits)>=(double)(1);
+                    needevd = needevd || (double)(hqrnd.hqrnduniformr(s.rs, _params))<(double)(updateits-(int)Math.Floor(updateits));
+                    if( needevd )
+                    {
+                        apserv.inc(ref s.dbgcntevd, _params);
+                        s.nbasis = Math.Min(winw, s.topk);
+                        if( !evd.smatrixevd(s.xxt, winw, 1, true, ref s.sv, ref s.basis, _params) )
+                        {
+                            alglib.ap.assert(false, "SSA: SMatrixEVD failed");
+                        }
+                        for(i=0; i<=winw-1; i++)
+                        {
+                            k = winw-1-i;
+                            if( i>=k )
+                            {
+                                break;
+                            }
+                            v = s.sv[i];
+                            s.sv[i] = s.sv[k];
+                            s.sv[k] = v;
+                            for(j=0; j<=winw-1; j++)
+                            {
+                                v = s.basis[j,i];
+                                s.basis[j,i] = s.basis[j,k];
+                                s.basis[j,k] = v;
+                            }
+                        }
+                        for(i=0; i<=s.nbasis-1; i++)
+                        {
+                            s.sv[i] = Math.Sqrt(Math.Max(s.sv[i], 0.0));
+                        }
+                        apserv.rmatrixsetlengthatleast(ref s.basist, s.nbasis, winw, _params);
+                        ablas.rmatrixtranspose(winw, s.nbasis, s.basis, 0, 0, s.basist, 0, 0, _params);
+                    }
+                }
+                else
+                {
+                    if( s.algotype==3 )
+                    {
+                        
+                        //
+                        // Real-time top-K.
+                        //
+                        // Determine actual number of basis components, prepare subspace
+                        // solver (either create from scratch or reuse).
+                        //
+                        // Update is always performed for invalid basis; for a valid basis
+                        // it is performed with probability UpdateIts.
+                        //
+                        if( s.rtpowerup==1 )
+                        {
+                            subspaceits = s.defaultsubspaceits;
+                        }
+                        else
+                        {
+                            subspaceits = 3;
+                        }
+                        if( appendlen>0 )
+                        {
+                            alglib.ap.assert(s.arebasisandsolvervalid, "SSA: integrity check in UpdateBasis() failed / srg6f");
+                            alglib.ap.assert((double)(updateits)>=(double)(0), "SSA: integrity check in UpdateBasis() failed / srg4f");
+                            subspaceits = (int)Math.Floor(updateits);
+                            if( (double)(hqrnd.hqrnduniformr(s.rs, _params))<(double)(updateits-(int)Math.Floor(updateits)) )
+                            {
+                                apserv.inc(ref subspaceits, _params);
+                            }
+                            alglib.ap.assert(subspaceits>=0, "SSA: integrity check in UpdateBasis() failed / srg9f");
+                        }
+                        
+                        //
+                        // Dequeue pending dataset and merge it into XXT.
+                        //
+                        // Dequeuing is done only for appends, and only when we have
+                        // non-empty queue.
+                        //
+                        if( appendlen>0 && s.rtqueuecnt>0 )
+                        {
+                            realtimedequeue(s, 1.0, Math.Min(s.rtqueuechunk, s.rtqueuecnt), _params);
+                        }
+                        
+                        //
+                        // Now, proceed to solver
+                        //
+                        if( subspaceits>0 )
+                        {
+                            if( appendlen==0 )
+                            {
+                                s.nbasis = Math.Min(winw, s.topk);
+                                evd.eigsubspacecreatebuf(winw, s.nbasis, s.solver, _params);
+                            }
+                            else
+                            {
+                                evd.eigsubspacesetwarmstart(s.solver, true, _params);
+                            }
+                            evd.eigsubspacesetcond(s.solver, 0.0, subspaceits, _params);
+                            
+                            //
+                            // Perform initial basis estimation
+                            //
+                            apserv.inc(ref s.dbgcntevd, _params);
+                            evd.eigsubspaceoocstart(s.solver, 0, _params);
+                            while( evd.eigsubspaceooccontinue(s.solver, _params) )
+                            {
+                                evd.eigsubspaceoocgetrequestinfo(s.solver, ref requesttype, ref requestsize, _params);
+                                alglib.ap.assert(requesttype==0, "SSA: integrity check in UpdateBasis() failed / 346372");
+                                ablas.rmatrixgemm(winw, requestsize, winw, 1.0, s.xxt, 0, 0, 0, s.solver.x, 0, 0, 0, 0.0, s.solver.ax, 0, 0, _params);
+                            }
+                            evd.eigsubspaceoocstop(s.solver, ref s.sv, ref s.basis, s.solverrep, _params);
+                            for(i=0; i<=s.nbasis-1; i++)
+                            {
+                                s.sv[i] = Math.Sqrt(Math.Max(s.sv[i], 0.0));
+                            }
+                            apserv.rmatrixsetlengthatleast(ref s.basist, s.nbasis, winw, _params);
+                            ablas.rmatrixtranspose(winw, s.nbasis, s.basis, 0, 0, s.basist, 0, 0, _params);
+                        }
+                    }
+                    else
+                    {
+                        alglib.ap.assert(false, "SSA: integrity check in UpdateBasis() failed / dfgs34");
+                    }
+                }
+            }
+            
+            //
+            // Update recurrent relation
+            //
+            apserv.rvectorsetlengthatleast(ref s.forecasta, Math.Max(winw-1, 1), _params);
+            degeneraterecurrence = false;
+            if( winw>1 )
+            {
+                
+                //
+                // Non-degenerate case
+                //
+                apserv.rvectorsetlengthatleast(ref s.tmp0, s.nbasis, _params);
+                nu2 = 0.0;
+                for(i=0; i<=s.nbasis-1; i++)
+                {
+                    v = s.basist[i,winw-1];
+                    s.tmp0[i] = v;
+                    nu2 = nu2+v*v;
+                }
+                if( (double)(nu2)<(double)(1-1000*math.machineepsilon) )
+                {
+                    ablas.rmatrixgemv(winw-1, s.nbasis, 1/(1-nu2), s.basist, 0, 0, 1, s.tmp0, 0, 0.0, s.forecasta, 0, _params);
+                }
+                else
+                {
+                    degeneraterecurrence = true;
+                }
+            }
+            else
+            {
+                degeneraterecurrence = true;
+            }
+            if( degeneraterecurrence )
+            {
+                for(i=0; i<=Math.Max(winw-1, 1)-1; i++)
+                {
+                    s.forecasta[i] = 0.0;
+                }
+                s.forecasta[Math.Max(winw-1, 1)-1] = 1.0;
+            }
+            
+            //
+            // Set validity flag
+            //
+            s.arebasisandsolvervalid = true;
+        }
+
+
+        /*************************************************************************
+        This function performs analysis using current basis. It assumes and checks
+        that validity flag AreBasisAndSolverValid is set.
+
+        INPUT PARAMETERS:
+            S                   -   model
+            Data                -   array which holds data in elements [I0,I1):
+                                    * right bound is not included.
+                                    * I1-I0>=WindowWidth (assertion is performed).
+            Trend               -   preallocated output array, large enough
+            Noise               -   preallocated output array, large enough
+            Offs                -   offset in Trend/Noise where result is stored;
+                                    I1-I0 elements are written starting at offset
+                                    Offs.
+                                    
+        OUTPUT PARAMETERS:
+            Trend, Noise - processing results
+                                    
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void analyzesequence(ssamodel s,
+            double[] data,
+            int i0,
+            int i1,
+            double[] trend,
+            double[] noise,
+            int offs,
+            alglib.xparams _params)
+        {
+            int winw = 0;
+            int nwindows = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int cnt = 0;
+            int batchstart = 0;
+            int batchlimit = 0;
+            int batchsize = 0;
+
+            alglib.ap.assert(s.arebasisandsolvervalid, "AnalyzeSequence: integrity check failed / d84sz0");
+            alglib.ap.assert(i1-i0>=s.windowwidth, "AnalyzeSequence: integrity check failed / d84sz1");
+            alglib.ap.assert(s.nbasis>=1, "AnalyzeSequence: integrity check failed / d84sz2");
+            nwindows = i1-i0-s.windowwidth+1;
+            winw = s.windowwidth;
+            batchlimit = Math.Max(nwindows, 1);
+            if( s.memorylimit>0 )
+            {
+                batchlimit = Math.Min(batchlimit, Math.Max(s.memorylimit/winw, 4*winw));
+            }
+            
+            //
+            // Zero-initialize trend and counts
+            //
+            cnt = i1-i0;
+            apserv.ivectorsetlengthatleast(ref s.aseqcounts, cnt, _params);
+            for(i=0; i<=cnt-1; i++)
+            {
+                s.aseqcounts[i] = 0;
+                trend[offs+i] = 0.0;
+            }
+            
+            //
+            // Reset temporaries if algorithm settings changed since last round
+            //
+            if( alglib.ap.cols(s.aseqtrajectory)!=winw )
+            {
+                s.aseqtrajectory = new double[0, 0];
+            }
+            if( alglib.ap.cols(s.aseqtbproduct)!=s.nbasis )
+            {
+                s.aseqtbproduct = new double[0, 0];
+            }
+            
+            //
+            // Perform batch processing
+            //
+            apserv.rmatrixsetlengthatleast(ref s.aseqtrajectory, batchlimit, winw, _params);
+            apserv.rmatrixsetlengthatleast(ref s.aseqtbproduct, batchlimit, s.nbasis, _params);
+            batchsize = 0;
+            batchstart = offs;
+            for(i=0; i<=nwindows-1; i++)
+            {
+                
+                //
+                // Enqueue next row of trajectory matrix
+                //
+                if( batchsize==0 )
+                {
+                    batchstart = i;
+                }
+                for(j=0; j<=winw-1; j++)
+                {
+                    s.aseqtrajectory[batchsize,j] = data[i0+i+j];
+                }
+                apserv.inc(ref batchsize, _params);
+                
+                //
+                // Process batch
+                //
+                if( batchsize==batchlimit || i==nwindows-1 )
+                {
+                    
+                    //
+                    // Project onto basis
+                    //
+                    ablas.rmatrixgemm(batchsize, s.nbasis, winw, 1.0, s.aseqtrajectory, 0, 0, 0, s.basist, 0, 0, 1, 0.0, s.aseqtbproduct, 0, 0, _params);
+                    ablas.rmatrixgemm(batchsize, winw, s.nbasis, 1.0, s.aseqtbproduct, 0, 0, 0, s.basist, 0, 0, 0, 0.0, s.aseqtrajectory, 0, 0, _params);
+                    
+                    //
+                    // Hankelize
+                    //
+                    for(k=0; k<=batchsize-1; k++)
+                    {
+                        for(j=0; j<=winw-1; j++)
+                        {
+                            trend[offs+batchstart+k+j] = trend[offs+batchstart+k+j]+s.aseqtrajectory[k,j];
+                            s.aseqcounts[batchstart+k+j] = s.aseqcounts[batchstart+k+j]+1;
+                        }
+                    }
+                    
+                    //
+                    // Reset batch size
+                    //
+                    batchsize = 0;
+                }
+            }
+            for(i=0; i<=cnt-1; i++)
+            {
+                trend[offs+i] = trend[offs+i]/s.aseqcounts[i];
+            }
+            
+            //
+            // Output noise
+            //
+            for(i=0; i<=cnt-1; i++)
+            {
+                noise[offs+i] = data[i0+i]-trend[offs+i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function performs  averaged  forecasting.  It  assumes  that basis is
+        already built, everything is valid and checked. See  comments  on  similar
+        public functions to find out more about averaged predictions.
+
+        INPUT PARAMETERS:
+            S                   -   model
+            Data                -   array which holds data in elements [I0,I1):
+                                    * right bound is not included.
+                                    * I1-I0>=WindowWidth (assertion is performed).
+            M                   -   number  of  sliding  windows  to combine, M>=1. If
+                                    your dataset has less than M sliding windows, this
+                                    parameter will be silently reduced.
+            ForecastLen         -   number of ticks to predict, ForecastLen>=1
+            Trend               -   preallocated output array, large enough
+            Offs                -   offset in Trend where result is stored;
+                                    I1-I0 elements are written starting at offset
+                                    Offs.
+
+        OUTPUT PARAMETERS:
+            Trend           -   array[ForecastLen], forecasted trend
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void forecastavgsequence(ssamodel s,
+            double[] data,
+            int i0,
+            int i1,
+            int m,
+            int forecastlen,
+            bool smooth,
+            double[] trend,
+            int offs,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int winw = 0;
+
+            alglib.ap.assert(s.arebasisandsolvervalid, "ForecastAvgSequence: integrity check failed / d84sz0");
+            alglib.ap.assert(i1-i0-s.windowwidth+1>=m, "ForecastAvgSequence: integrity check failed / d84sz1");
+            alglib.ap.assert(s.nbasis>=1, "ForecastAvgSequence: integrity check failed / d84sz2");
+            alglib.ap.assert(s.windowwidth>=2, "ForecastAvgSequence: integrity check failed / 5tgdg5");
+            alglib.ap.assert(s.windowwidth>s.nbasis, "ForecastAvgSequence: integrity check failed / d5g56w");
+            winw = s.windowwidth;
+            
+            //
+            // Prepare M synchronized predictions for the last known tick
+            // (last one is an actual value of the trend, previous M-1 predictions
+            // are predictions from differently positioned sliding windows).
+            //
+            apserv.rmatrixsetlengthatleast(ref s.fctrendm, m, winw, _params);
+            apserv.rvectorsetlengthatleast(ref s.tmp0, Math.Max(m, s.nbasis), _params);
+            apserv.rvectorsetlengthatleast(ref s.tmp1, winw, _params);
+            for(k=0; k<=m-1; k++)
+            {
+                
+                //
+                // Perform prediction for rows [0,K-1]
+                //
+                ablas.rmatrixgemv(k, winw-1, 1.0, s.fctrendm, 0, 1, 0, s.forecasta, 0, 0.0, s.tmp0, 0, _params);
+                for(i=0; i<=k-1; i++)
+                {
+                    for(j=1; j<=winw-1; j++)
+                    {
+                        s.fctrendm[i,j-1] = s.fctrendm[i,j];
+                    }
+                    s.fctrendm[i,winw-1] = s.tmp0[i];
+                }
+                
+                //
+                // Perform trend extraction for row K, add it to dataset
+                //
+                if( smooth )
+                {
+                    ablas.rmatrixgemv(s.nbasis, winw, 1.0, s.basist, 0, 0, 0, data, i1-winw-(m-1-k), 0.0, s.tmp0, 0, _params);
+                    ablas.rmatrixgemv(s.windowwidth, s.nbasis, 1.0, s.basis, 0, 0, 0, s.tmp0, 0, 0.0, s.tmp1, 0, _params);
+                    for(j=0; j<=winw-1; j++)
+                    {
+                        s.fctrendm[k,j] = s.tmp1[j];
+                    }
+                }
+                else
+                {
+                    for(j=0; j<=winw-1; j++)
+                    {
+                        s.fctrendm[k,j] = data[i1-winw-(m-1-k)+j];
+                    }
+                }
+            }
+            
+            //
+            // Now we have M synchronized predictions of the sequence state at the last
+            // know moment (last "prediction" is just a copy of the trend). Let's start
+            // batch prediction!
+            //
+            for(k=0; k<=forecastlen-1; k++)
+            {
+                ablas.rmatrixgemv(m, winw-1, 1.0, s.fctrendm, 0, 1, 0, s.forecasta, 0, 0.0, s.tmp0, 0, _params);
+                trend[offs+k] = 0.0;
+                for(i=0; i<=m-1; i++)
+                {
+                    for(j=1; j<=winw-1; j++)
+                    {
+                        s.fctrendm[i,j-1] = s.fctrendm[i,j];
+                    }
+                    s.fctrendm[i,winw-1] = s.tmp0[i];
+                    trend[offs+k] = trend[offs+k]+s.tmp0[i];
+                }
+                trend[offs+k] = trend[offs+k]/m;
+            }
+        }
+
+
+        /*************************************************************************
+        This function extracts updates from real-time queue and  applies  them  to
+        the S.XXT matrix. XXT is premultiplied by  Beta,  which  can  be  0.0  for
+        initial creation, 1.0 for subsequent updates, or even within (0,1) for some
+        kind of updates with decay.
+
+        INPUT PARAMETERS:
+            S                   -   model
+            Beta                -   >=0, coefficient to premultiply XXT
+            Cnt                 -   0<Cnt<=S.RTQueueCnt, number of updates to extract
+                                    from the end of the queue
+                                    
+        OUTPUT PARAMETERS:
+            S                   -   S.XXT updated, S.RTQueueCnt decreased
+
+          -- ALGLIB --
+             Copyright 30.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void realtimedequeue(ssamodel s,
+            double beta,
+            int cnt,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int winw = 0;
+
+            alglib.ap.assert(cnt>0, "SSA: RealTimeDequeue() integrity check failed / 43tdv");
+            alglib.ap.assert(math.isfinite(beta) && (double)(beta)>=(double)(0), "SSA: RealTimeDequeue() integrity check failed / 5gdg6");
+            alglib.ap.assert(cnt<=s.rtqueuecnt, "SSA: RealTimeDequeue() integrity check failed / 547yh");
+            alglib.ap.assert(alglib.ap.cols(s.xxt)>=s.windowwidth, "SSA: RealTimeDequeue() integrity check failed / 54bf4");
+            alglib.ap.assert(alglib.ap.rows(s.xxt)>=s.windowwidth, "SSA: RealTimeDequeue() integrity check failed / 9gdfn");
+            winw = s.windowwidth;
+            
+            //
+            // Premultiply XXT by Beta
+            //
+            if( (double)(beta)!=(double)(0) )
+            {
+                for(i=0; i<=winw-1; i++)
+                {
+                    for(j=0; j<=winw-1; j++)
+                    {
+                        s.xxt[i,j] = s.xxt[i,j]*beta;
+                    }
+                }
+            }
+            else
+            {
+                for(i=0; i<=winw-1; i++)
+                {
+                    for(j=0; j<=winw-1; j++)
+                    {
+                        s.xxt[i,j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // Dequeue
+            //
+            updatexxtprepare(s, cnt, winw, s.memorylimit, _params);
+            for(i=0; i<=cnt-1; i++)
+            {
+                updatexxtsend(s, s.sequencedata, s.rtqueue[s.rtqueuecnt-1], s.xxt, _params);
+                apserv.dec(ref s.rtqueuecnt, _params);
+            }
+            updatexxtfinalize(s, s.xxt, _params);
+        }
+
+
+        /*************************************************************************
+        This function prepares batch buffer for XXT update. The idea  is  that  we
+        send a stream of "XXT += u*u'" updates, and we want to package  them  into
+        one big matrix update U*U', applied with SYRK() kernel, but U can  consume
+        too much memory, so we want to transparently divide it  into  few  smaller
+        chunks.
+
+        This set of functions solves this problem:
+        * UpdateXXTPrepare() prepares temporary buffers
+        * UpdateXXTSend() sends next u to the buffer, possibly initiating next SYRK()
+        * UpdateXXTFinalize() performs last SYRK() update
+
+        INPUT PARAMETERS:
+            S                   -   model, only fields with UX prefix are used
+            UpdateSize          -   number of updates
+            WindowWidth         -   window width, >0
+            MemoryLimit         -   memory limit, non-positive value means no limit
+                                    
+        OUTPUT PARAMETERS:
+            S                   -   UX temporaries updated
+
+          -- ALGLIB --
+             Copyright 20.12.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void updatexxtprepare(ssamodel s,
+            int updatesize,
+            int windowwidth,
+            int memorylimit,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(windowwidth>0, "UpdateXXTPrepare: WinW<=0");
+            s.uxbatchlimit = Math.Max(updatesize, 1);
+            if( memorylimit>0 )
+            {
+                s.uxbatchlimit = Math.Min(s.uxbatchlimit, Math.Max(memorylimit/windowwidth, 4*windowwidth));
+            }
+            s.uxbatchwidth = windowwidth;
+            s.uxbatchsize = 0;
+            if( alglib.ap.cols(s.uxbatch)!=windowwidth )
+            {
+                s.uxbatch = new double[0, 0];
+            }
+            apserv.rmatrixsetlengthatleast(ref s.uxbatch, s.uxbatchlimit, windowwidth, _params);
+        }
+
+
+        /*************************************************************************
+        This function sends update u*u' to the batch buffer.
+
+        INPUT PARAMETERS:
+            S                   -   model, only fields with UX prefix are used
+            U                   -   WindowWidth-sized update, starts at I0
+            I0                  -   starting position for update
+                                    
+        OUTPUT PARAMETERS:
+            S                   -   UX temporaries updated
+            XXT                 -   array[WindowWidth,WindowWidth], in the middle
+                                    of update. All intermediate updates are
+                                    applied to the upper triangle.
+
+          -- ALGLIB --
+             Copyright 20.12.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void updatexxtsend(ssamodel s,
+            double[] u,
+            int i0,
+            double[,] xxt,
+            alglib.xparams _params)
+        {
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert(i0+s.uxbatchwidth-1<alglib.ap.len(u), "UpdateXXTSend: incorrect U size");
+            alglib.ap.assert(s.uxbatchsize>=0, "UpdateXXTSend: integrity check failure");
+            alglib.ap.assert(s.uxbatchsize<=s.uxbatchlimit, "UpdateXXTSend: integrity check failure");
+            alglib.ap.assert(s.uxbatchlimit>=1, "UpdateXXTSend: integrity check failure");
+            
+            //
+            // Send pending batch if full
+            //
+            if( s.uxbatchsize==s.uxbatchlimit )
+            {
+                ablas.rmatrixsyrk(s.uxbatchwidth, s.uxbatchsize, 1.0, s.uxbatch, 0, 0, 2, 1.0, xxt, 0, 0, true, _params);
+                s.uxbatchsize = 0;
+            }
+            
+            //
+            // Append update to batch
+            //
+            i1_ = (i0) - (0);
+            for(i_=0; i_<=s.uxbatchwidth-1;i_++)
+            {
+                s.uxbatch[s.uxbatchsize,i_] = u[i_+i1_];
+            }
+            apserv.inc(ref s.uxbatchsize, _params);
+        }
+
+
+        /*************************************************************************
+        This function finalizes batch buffer. Call it after the last update.
+
+        INPUT PARAMETERS:
+            S                   -   model, only fields with UX prefix are used
+                                    
+        OUTPUT PARAMETERS:
+            S                   -   UX temporaries updated
+            XXT                 -   array[WindowWidth,WindowWidth], updated with
+                                    all previous updates, both triangles of the
+                                    symmetric matrix are present.
+
+          -- ALGLIB --
+             Copyright 20.12.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void updatexxtfinalize(ssamodel s,
+            double[,] xxt,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(s.uxbatchsize>=0, "UpdateXXTFinalize: integrity check failure");
+            alglib.ap.assert(s.uxbatchsize<=s.uxbatchlimit, "UpdateXXTFinalize: integrity check failure");
+            alglib.ap.assert(s.uxbatchlimit>=1, "UpdateXXTFinalize: integrity check failure");
+            if( s.uxbatchsize>0 )
+            {
+                ablas.rmatrixsyrk(s.uxbatchwidth, s.uxbatchsize, 1.0, s.uxbatch, 0, 0, 2, 1.0, s.xxt, 0, 0, true, _params);
+                s.uxbatchsize = 0;
+            }
+            ablas.rmatrixenforcesymmetricity(s.xxt, s.uxbatchwidth, true, _params);
+        }
+
+
+    }
+    public class linreg
+    {
+        public class linearmodel : apobject
+        {
+            public double[] w;
+            public linearmodel()
+            {
+                init();
+            }
+            public override void init()
+            {
+                w = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                linearmodel _result = new linearmodel();
+                _result.w = (double[])w.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        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 : apobject
+        {
+            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()
+            {
+                init();
+            }
+            public override void init()
+            {
+                c = new double[0,0];
+                cvdefects = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                lrreport _result = new lrreport();
+                _result.c = (double[,])c.Clone();
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.avgrelerror = avgrelerror;
+                _result.cvrmserror = cvrmserror;
+                _result.cvavgerror = cvavgerror;
+                _result.cvavgrelerror = cvavgrelerror;
+                _result.ncvdefects = ncvdefects;
+                _result.cvdefects = (int[])cvdefects.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        Internal linear regression subroutine
+        *************************************************************************/
+        private static void lrinternal(double[,] xy,
+            double[] s,
+            int npoints,
+            int nvars,
+            ref int info,
+            linearmodel lm,
+            lrreport ar,
+            alglib.xparams _params)
+        {
+            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, _params) )
+            {
+                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, _params);
+                ar.avgerror = lravgerror(lm, xy, npoints, _params);
+                ar.avgrelerror = lravgrelerror(lm, xy, npoints, _params);
+                ar.cvrmserror = ar.rmserror;
+                ar.cvavgerror = ar.avgerror;
+                ar.cvavgrelerror = ar.avgrelerror;
+                ar.ncvdefects = 0;
+                ar.cvdefects = new int[nvars-1+1];
+                for(i=0; i<=nvars-1; i++)
+                {
+                    ar.cvdefects[i] = -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, _params);
+                        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];
+                        }
+                        for(j=ar.ncvdefects; j<=nvars-1; j++)
+                        {
+                            ar.cvdefects[j] = -1;
+                        }
+                        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, _params);
+                        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, _params);
+                        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<=nvars-1; i++)
+            {
+                ar.cvdefects[i] = -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,
+            alglib.xparams _params)
+        {
+            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, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params);
+                alglib.ap.assert(info==1, "FilterLRMA: internal error");
+                x[i] = a+b*(m-1);
+            }
+        }
+
+
+    }
+    public class logit
+    {
+        public class logitmodel : apobject
+        {
+            public double[] w;
+            public logitmodel()
+            {
+                init();
+            }
+            public override void init()
+            {
+                w = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                logitmodel _result = new logitmodel();
+                _result.w = (double[])w.Clone();
+                return _result;
+            }
+        };
+
+
+        public class logitmcstate : apobject
+        {
+            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 logitmcstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                logitmcstate _result = new logitmcstate();
+                _result.brackt = brackt;
+                _result.stage1 = stage1;
+                _result.infoc = infoc;
+                _result.dg = dg;
+                _result.dgm = dgm;
+                _result.dginit = dginit;
+                _result.dgtest = dgtest;
+                _result.dgx = dgx;
+                _result.dgxm = dgxm;
+                _result.dgy = dgy;
+                _result.dgym = dgym;
+                _result.finit = finit;
+                _result.ftest1 = ftest1;
+                _result.fm = fm;
+                _result.fx = fx;
+                _result.fxm = fxm;
+                _result.fy = fy;
+                _result.fym = fym;
+                _result.stx = stx;
+                _result.sty = sty;
+                _result.stmin = stmin;
+                _result.stmax = stmax;
+                _result.width = width;
+                _result.width1 = width1;
+                _result.xtrapf = xtrapf;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        MNLReport structure contains information about training process:
+        * NGrad     -   number of gradient calculations
+        * NHess     -   number of Hessian calculations
+        *************************************************************************/
+        public class mnlreport : apobject
+        {
+            public int ngrad;
+            public int nhess;
+            public mnlreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                mnlreport _result = new mnlreport();
+                _result.ngrad = ngrad;
+                _result.nhess = nhess;
+                return _result;
+            }
+        };
+
+
+
+
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int ssize = 0;
+            bool allsame = new bool();
+            int offs = 0;
+            double decay = 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;
+            directdensesolvers.densesolverreport solverrep = new directdensesolvers.densesolverreport();
+            int i_ = 0;
+            int i1_ = 0;
+
+            info = 0;
+
+            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
+            //
+            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;
+            
+            //
+            // 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, _params);
+            mlpbase.mlpinitpreprocessor(network, xy, npoints, _params);
+            mlpbase.mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            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, _params);
+                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, _params);
+                while( mcstage!=0 )
+                {
+                    mlpbase.mlpgradnbatch(network, xy, npoints, ref e, ref g, _params);
+                    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, _params);
+                }
+            }
+            
+            //
+            // 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, _params);
+                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, _params);
+                directdensesolvers.spdmatrixcholeskysolve(h, wcount, false, g, ref solverinfo, solverrep, ref wdir, _params);
+                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, _params);
+                while( mcstage!=0 )
+                {
+                    mlpbase.mlpgradnbatch(network, xy, npoints, ref e, ref g, _params);
+                    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, _params);
+                }
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            y = new double[0];
+
+            mnlprocess(lm, x, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int offs = 0;
+            int i = 0;
+            int ssize = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = (double)mnlclserror(lm, xy, npoints, _params)/(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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                
+                //
+                // 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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    workx[i_] = xy[i,i_];
+                }
+                mnlprocess(lm, workx, ref y, _params);
+                dy[0] = xy[i,nvars];
+                bdss.dserraccumulate(ref buf, y, dy, _params);
+            }
+            bdss.dserrfinish(ref buf, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                        
+                        //
+                        //           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, _params);
+                    }
+                    
+                    //
+                    //        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,
+            alglib.xparams _params)
+        {
+            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 : apobject
+        {
+            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()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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];
+            }
+            public override alglib.apobject make_copy()
+            {
+                mcpdstate _result = new mcpdstate();
+                _result.n = n;
+                _result.states = (int[])states.Clone();
+                _result.npairs = npairs;
+                _result.data = (double[,])data.Clone();
+                _result.ec = (double[,])ec.Clone();
+                _result.bndl = (double[,])bndl.Clone();
+                _result.bndu = (double[,])bndu.Clone();
+                _result.c = (double[,])c.Clone();
+                _result.ct = (int[])ct.Clone();
+                _result.ccnt = ccnt;
+                _result.pw = (double[])pw.Clone();
+                _result.priorp = (double[,])priorp.Clone();
+                _result.regterm = regterm;
+                _result.bs = (minbleic.minbleicstate)bs.make_copy();
+                _result.repinneriterationscount = repinneriterationscount;
+                _result.repouteriterationscount = repouteriterationscount;
+                _result.repnfev = repnfev;
+                _result.repterminationtype = repterminationtype;
+                _result.br = (minbleic.minbleicreport)br.make_copy();
+                _result.tmpp = (double[])tmpp.Clone();
+                _result.effectivew = (double[])effectivew.Clone();
+                _result.effectivebndl = (double[])effectivebndl.Clone();
+                _result.effectivebndu = (double[])effectivebndu.Clone();
+                _result.effectivec = (double[,])effectivec.Clone();
+                _result.effectivect = (int[])effectivect.Clone();
+                _result.h = (double[])h.Clone();
+                _result.p = (double[,])p.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        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 : apobject
+        {
+            public int inneriterationscount;
+            public int outeriterationscount;
+            public int nfev;
+            public int terminationtype;
+            public mcpdreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                mcpdreport _result = new mcpdreport();
+                _result.inneriterationscount = inneriterationscount;
+                _result.outeriterationscount = outeriterationscount;
+                _result.nfev = nfev;
+                _result.terminationtype = terminationtype;
+                return _result;
+            }
+        };
+
+
+
+
+        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.xparams _params)
+        {
+            alglib.ap.assert(n>=1, "MCPDCreate: N<1");
+            mcpdinit(n, -1, -1, s, _params);
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params);
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params), "MCPDSetLC: C contains infinite or NaN values!");
+            apserv.rmatrixsetlengthatleast(ref s.c, k, n*n+1, _params);
+            apserv.ivectorsetlengthatleast(ref s.ct, k, _params);
+            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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            apserv.ivectorsetlengthatleast(ref s.effectivect, s.ccnt+n, _params);
+            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, _params);
+            minbleic.minbleicsetbc(s.bs, s.effectivebndl, s.effectivebndu, _params);
+            minbleic.minbleicsetlc(s.bs, s.effectivec, s.effectivect, ccnt, _params);
+            minbleic.minbleicsetcond(s.bs, 0.0, 0.0, xtol, 0, _params);
+            minbleic.minbleicsetprecdiag(s.bs, s.h, _params);
+            
+            //
+            // solve problem
+            //
+            while( minbleic.minbleiciteration(s.bs, _params) )
+            {
+                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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+    }
+    public class mlpe
+    {
+        /*************************************************************************
+        Neural networks ensemble
+        *************************************************************************/
+        public class mlpensemble : apobject
+        {
+            public int ensemblesize;
+            public double[] weights;
+            public double[] columnmeans;
+            public double[] columnsigmas;
+            public mlpbase.multilayerperceptron network;
+            public double[] y;
+            public mlpensemble()
+            {
+                init();
+            }
+            public override void init()
+            {
+                weights = new double[0];
+                columnmeans = new double[0];
+                columnsigmas = new double[0];
+                network = new mlpbase.multilayerperceptron();
+                y = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                mlpensemble _result = new mlpensemble();
+                _result.ensemblesize = ensemblesize;
+                _result.weights = (double[])weights.Clone();
+                _result.columnmeans = (double[])columnmeans.Clone();
+                _result.columnsigmas = (double[])columnsigmas.Clone();
+                _result.network = (mlpbase.multilayerperceptron)network.make_copy();
+                _result.y = (double[])y.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        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,
+            alglib.xparams _params)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreate0(nin, nout, net, _params);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreate1(nin, nhid, nout, net, _params);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreate2(nin, nhid1, nhid2, nout, net, _params);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreateb0(nin, nout, b, d, net, _params);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreateb1(nin, nhid, nout, b, d, net, _params);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreateb2(nin, nhid1, nhid2, nout, b, d, net, _params);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreater0(nin, nout, a, b, net, _params);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreater1(nin, nhid, nout, a, b, net, _params);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreater2(nin, nhid1, nhid2, nout, a, b, net, _params);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreatec0(nin, nout, net, _params);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreatec1(nin, nhid, nout, net, _params);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            mlpbase.multilayerperceptron net = new mlpbase.multilayerperceptron();
+
+            mlpbase.mlpcreatec2(nin, nhid1, nhid2, nout, net, _params);
+            mlpecreatefromnetwork(net, ensemblesize, ensemble, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            
+            //
+            // network properties
+            //
+            if( mlpbase.mlpissoftmax(network, _params) )
+            {
+                ccount = mlpbase.mlpgetinputscount(ensemble.network, _params);
+            }
+            else
+            {
+                ccount = mlpbase.mlpgetinputscount(ensemble.network, _params)+mlpbase.mlpgetoutputscount(ensemble.network, _params);
+            }
+            wcount = mlpbase.mlpgetweightscount(ensemble.network, _params);
+            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, _params)];
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int ccount = 0;
+            int wcount = 0;
+            int i_ = 0;
+
+            
+            //
+            // Unload info
+            //
+            if( mlpbase.mlpissoftmax(ensemble1.network, _params) )
+            {
+                ccount = mlpbase.mlpgetinputscount(ensemble1.network, _params);
+            }
+            else
+            {
+                ccount = mlpbase.mlpgetinputscount(ensemble1.network, _params)+mlpbase.mlpgetoutputscount(ensemble1.network, _params);
+            }
+            wcount = mlpbase.mlpgetweightscount(ensemble1.network, _params);
+            
+            //
+            // 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, _params)];
+            
+            //
+            // 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, _params);
+        }
+
+
+        /*************************************************************************
+        Randomization of MLP ensemble
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlperandomize(mlpensemble ensemble,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int wcount = 0;
+
+            wcount = mlpbase.mlpgetweightscount(ensemble.network, _params);
+            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,
+            alglib.xparams _params)
+        {
+            nin = 0;
+            nout = 0;
+
+            nin = mlpbase.mlpgetinputscount(ensemble.network, _params);
+            nout = mlpbase.mlpgetoutputscount(ensemble.network, _params);
+        }
+
+
+        /*************************************************************************
+        Return normalization type (whether ensemble is SOFTMAX-normalized or not).
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool mlpeissoftmax(mlpensemble ensemble,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = mlpbase.mlpissoftmax(ensemble.network, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params) )
+            {
+                y = new double[mlpbase.mlpgetoutputscount(ensemble.network, _params)];
+            }
+            es = ensemble.ensemblesize;
+            wc = mlpbase.mlpgetweightscount(ensemble.network, _params);
+            if( mlpbase.mlpissoftmax(ensemble.network, _params) )
+            {
+                cc = mlpbase.mlpgetinputscount(ensemble.network, _params);
+            }
+            else
+            {
+                cc = mlpbase.mlpgetinputscount(ensemble.network, _params)+mlpbase.mlpgetoutputscount(ensemble.network, _params);
+            }
+            v = (double)1/(double)es;
+            nout = mlpbase.mlpgetoutputscount(ensemble.network, _params);
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            y = new double[0];
+
+            mlpeprocess(ensemble, x, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        Calculation of all types of errors
+
+          -- ALGLIB --
+             Copyright 17.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpeallerrorsx(mlpensemble ensemble,
+            double[,] densexy,
+            sparse.sparsematrix sparsexy,
+            int datasetsize,
+            int datasettype,
+            int[] idx,
+            int subset0,
+            int subset1,
+            int subsettype,
+            alglib.smp.shared_pool buf,
+            mlpbase.modelerrors rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int nin = 0;
+            int nout = 0;
+            bool iscls = new bool();
+            int srcidx = 0;
+            hpccores.mlpbuffers pbuf = null;
+            mlpbase.modelerrors rep0 = new mlpbase.modelerrors();
+            mlpbase.modelerrors rep1 = new mlpbase.modelerrors();
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Get network information
+            //
+            nin = mlpbase.mlpgetinputscount(ensemble.network, _params);
+            nout = mlpbase.mlpgetoutputscount(ensemble.network, _params);
+            iscls = mlpbase.mlpissoftmax(ensemble.network, _params);
+            
+            //
+            // Retrieve buffer, prepare, process data, recycle buffer
+            //
+            alglib.smp.ae_shared_pool_retrieve(buf, ref pbuf);
+            if( iscls )
+            {
+                bdss.dserrallocate(nout, ref pbuf.tmp0, _params);
+            }
+            else
+            {
+                bdss.dserrallocate(-nout, ref pbuf.tmp0, _params);
+            }
+            apserv.rvectorsetlengthatleast(ref pbuf.x, nin, _params);
+            apserv.rvectorsetlengthatleast(ref pbuf.y, nout, _params);
+            apserv.rvectorsetlengthatleast(ref pbuf.desiredy, nout, _params);
+            for(i=subset0; i<=subset1-1; i++)
+            {
+                srcidx = -1;
+                if( subsettype==0 )
+                {
+                    srcidx = i;
+                }
+                if( subsettype==1 )
+                {
+                    srcidx = idx[i];
+                }
+                alglib.ap.assert(srcidx>=0, "MLPEAllErrorsX: internal error");
+                if( datasettype==0 )
+                {
+                    for(i_=0; i_<=nin-1;i_++)
+                    {
+                        pbuf.x[i_] = densexy[srcidx,i_];
+                    }
+                }
+                if( datasettype==1 )
+                {
+                    sparse.sparsegetrow(sparsexy, srcidx, ref pbuf.x, _params);
+                }
+                mlpeprocess(ensemble, pbuf.x, ref pbuf.y, _params);
+                if( mlpbase.mlpissoftmax(ensemble.network, _params) )
+                {
+                    if( datasettype==0 )
+                    {
+                        pbuf.desiredy[0] = densexy[srcidx,nin];
+                    }
+                    if( datasettype==1 )
+                    {
+                        pbuf.desiredy[0] = sparse.sparseget(sparsexy, srcidx, nin, _params);
+                    }
+                }
+                else
+                {
+                    if( datasettype==0 )
+                    {
+                        i1_ = (nin) - (0);
+                        for(i_=0; i_<=nout-1;i_++)
+                        {
+                            pbuf.desiredy[i_] = densexy[srcidx,i_+i1_];
+                        }
+                    }
+                    if( datasettype==1 )
+                    {
+                        for(j=0; j<=nout-1; j++)
+                        {
+                            pbuf.desiredy[j] = sparse.sparseget(sparsexy, srcidx, nin+j, _params);
+                        }
+                    }
+                }
+                bdss.dserraccumulate(ref pbuf.tmp0, pbuf.y, pbuf.desiredy, _params);
+            }
+            bdss.dserrfinish(ref pbuf.tmp0, _params);
+            rep.relclserror = pbuf.tmp0[0];
+            rep.avgce = pbuf.tmp0[1]/Math.Log(2);
+            rep.rmserror = pbuf.tmp0[2];
+            rep.avgerror = pbuf.tmp0[3];
+            rep.avgrelerror = pbuf.tmp0[4];
+            alglib.smp.ae_shared_pool_recycle(buf, ref pbuf);
+        }
+
+
+        /*************************************************************************
+        Calculation of all types of errors on dataset given by sparse matrix
+
+          -- ALGLIB --
+             Copyright 10.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpeallerrorssparse(mlpensemble ensemble,
+            sparse.sparsematrix xy,
+            int npoints,
+            ref double relcls,
+            ref double avgce,
+            ref double rms,
+            ref double avg,
+            ref double avgrel,
+            alglib.xparams _params)
+        {
+            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, _params);
+            nout = mlpbase.mlpgetoutputscount(ensemble.network, _params);
+            if( mlpbase.mlpissoftmax(ensemble.network, _params) )
+            {
+                dy = new double[1];
+                bdss.dserrallocate(nout, ref buf, _params);
+            }
+            else
+            {
+                dy = new double[nout];
+                bdss.dserrallocate(-nout, ref buf, _params);
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                sparse.sparsegetrow(xy, i, ref workx, _params);
+                mlpeprocess(ensemble, workx, ref y, _params);
+                if( mlpbase.mlpissoftmax(ensemble.network, _params) )
+                {
+                    dy[0] = workx[nin];
+                }
+                else
+                {
+                    i1_ = (nin) - (0);
+                    for(i_=0; i_<=nout-1;i_++)
+                    {
+                        dy[i_] = workx[i_+i1_];
+                    }
+                }
+                bdss.dserraccumulate(ref buf, y, dy, _params);
+            }
+            bdss.dserrfinish(ref buf, _params);
+            relcls = buf[0];
+            avgce = buf[1];
+            rms = buf[2];
+            avg = buf[3];
+            avgrel = buf[4];
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            mlpbase.modelerrors rep = new mlpbase.modelerrors();
+
+            mlpeallerrorsx(ensemble, xy, ensemble.network.dummysxy, npoints, 0, ensemble.network.dummyidx, 0, npoints, 0, ensemble.network.buf, rep, _params);
+            result = rep.relclserror;
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            mlpbase.modelerrors rep = new mlpbase.modelerrors();
+
+            mlpeallerrorsx(ensemble, xy, ensemble.network.dummysxy, npoints, 0, ensemble.network.dummyidx, 0, npoints, 0, ensemble.network.buf, rep, _params);
+            result = rep.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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            mlpbase.modelerrors rep = new mlpbase.modelerrors();
+
+            mlpeallerrorsx(ensemble, xy, ensemble.network.dummysxy, npoints, 0, ensemble.network.dummyidx, 0, npoints, 0, ensemble.network.buf, rep, _params);
+            result = rep.rmserror;
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            mlpbase.modelerrors rep = new mlpbase.modelerrors();
+
+            mlpeallerrorsx(ensemble, xy, ensemble.network.dummysxy, npoints, 0, ensemble.network.dummyidx, 0, npoints, 0, ensemble.network.buf, rep, _params);
+            result = rep.avgerror;
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            mlpbase.modelerrors rep = new mlpbase.modelerrors();
+
+            mlpeallerrorsx(ensemble, xy, ensemble.network.dummysxy, npoints, 0, ensemble.network.dummyidx, 0, npoints, 0, ensemble.network.buf, rep, _params);
+            result = rep.avgrelerror;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 19.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpealloc(alglib.serializer s,
+            mlpensemble ensemble,
+            alglib.xparams _params)
+        {
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            apserv.allocrealarray(s, ensemble.weights, -1, _params);
+            apserv.allocrealarray(s, ensemble.columnmeans, -1, _params);
+            apserv.allocrealarray(s, ensemble.columnsigmas, -1, _params);
+            mlpbase.mlpalloc(s, ensemble.network, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpeserialize(alglib.serializer s,
+            mlpensemble ensemble,
+            alglib.xparams _params)
+        {
+            s.serialize_int(scodes.getmlpeserializationcode(_params));
+            s.serialize_int(mlpefirstversion);
+            s.serialize_int(ensemble.ensemblesize);
+            apserv.serializerealarray(s, ensemble.weights, -1, _params);
+            apserv.serializerealarray(s, ensemble.columnmeans, -1, _params);
+            apserv.serializerealarray(s, ensemble.columnsigmas, -1, _params);
+            mlpbase.mlpserialize(s, ensemble.network, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpeunserialize(alglib.serializer s,
+            mlpensemble ensemble,
+            alglib.xparams _params)
+        {
+            int i0 = 0;
+            int i1 = 0;
+
+            
+            //
+            // check correctness of header
+            //
+            i0 = s.unserialize_int();
+            alglib.ap.assert(i0==scodes.getmlpeserializationcode(_params), "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, _params);
+            apserv.unserializerealarray(s, ref ensemble.columnmeans, _params);
+            apserv.unserializerealarray(s, ref ensemble.columnsigmas, _params);
+            mlpbase.mlpunserialize(s, ensemble.network, _params);
+            
+            //
+            // Allocate termoraries
+            //
+            ensemble.y = new double[mlpbase.mlpgetoutputscount(ensemble.network, _params)];
+        }
+
+
+    }
+    public class mlptrain
+    {
+        /*************************************************************************
+        Training report:
+            * RelCLSError   -   fraction of misclassified cases.
+            * AvgCE         -   acerage cross-entropy
+            * RMSError      -   root-mean-square error
+            * AvgError      -   average error
+            * AvgRelError   -   average relative error
+            * NGrad         -   number of gradient calculations
+            * NHess         -   number of Hessian calculations
+            * NCholesky     -   number of Cholesky decompositions
+            
+        NOTE 1: RelCLSError/AvgCE are zero on regression problems.
+
+        NOTE 2: on classification problems  RMSError/AvgError/AvgRelError  contain
+                errors in prediction of posterior probabilities
+        *************************************************************************/
+        public class mlpreport : apobject
+        {
+            public double relclserror;
+            public double avgce;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public int ngrad;
+            public int nhess;
+            public int ncholesky;
+            public mlpreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                mlpreport _result = new mlpreport();
+                _result.relclserror = relclserror;
+                _result.avgce = avgce;
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.avgrelerror = avgrelerror;
+                _result.ngrad = ngrad;
+                _result.nhess = nhess;
+                _result.ncholesky = ncholesky;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Cross-validation estimates of generalization error
+        *************************************************************************/
+        public class mlpcvreport : apobject
+        {
+            public double relclserror;
+            public double avgce;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public mlpcvreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                mlpcvreport _result = new mlpcvreport();
+                _result.relclserror = relclserror;
+                _result.avgce = avgce;
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.avgrelerror = avgrelerror;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Temporary data structures used by following functions:
+        * TrainNetworkX
+        * StartTrainingX
+        * ContinueTrainingX
+
+        This structure contains:
+        * network being trained
+        * fully initialized LBFGS optimizer (we have to call MinLBFGSRestartFrom()
+          before using it; usually it is done by StartTrainingX() function).
+        * additional temporary arrays
+          
+        This structure should be initialized with InitMLPTrnSession() call.
+        *************************************************************************/
+        public class smlptrnsession : apobject
+        {
+            public double[] bestparameters;
+            public double bestrmserror;
+            public bool randomizenetwork;
+            public mlpbase.multilayerperceptron network;
+            public minlbfgs.minlbfgsstate optimizer;
+            public minlbfgs.minlbfgsreport optimizerrep;
+            public double[] wbuf0;
+            public double[] wbuf1;
+            public int[] allminibatches;
+            public int[] currentminibatch;
+            public rcommstate rstate;
+            public int algoused;
+            public int minibatchsize;
+            public hqrnd.hqrndstate generator;
+            public smlptrnsession()
+            {
+                init();
+            }
+            public override void init()
+            {
+                bestparameters = new double[0];
+                network = new mlpbase.multilayerperceptron();
+                optimizer = new minlbfgs.minlbfgsstate();
+                optimizerrep = new minlbfgs.minlbfgsreport();
+                wbuf0 = new double[0];
+                wbuf1 = new double[0];
+                allminibatches = new int[0];
+                currentminibatch = new int[0];
+                rstate = new rcommstate();
+                generator = new hqrnd.hqrndstate();
+            }
+            public override alglib.apobject make_copy()
+            {
+                smlptrnsession _result = new smlptrnsession();
+                _result.bestparameters = (double[])bestparameters.Clone();
+                _result.bestrmserror = bestrmserror;
+                _result.randomizenetwork = randomizenetwork;
+                _result.network = (mlpbase.multilayerperceptron)network.make_copy();
+                _result.optimizer = (minlbfgs.minlbfgsstate)optimizer.make_copy();
+                _result.optimizerrep = (minlbfgs.minlbfgsreport)optimizerrep.make_copy();
+                _result.wbuf0 = (double[])wbuf0.Clone();
+                _result.wbuf1 = (double[])wbuf1.Clone();
+                _result.allminibatches = (int[])allminibatches.Clone();
+                _result.currentminibatch = (int[])currentminibatch.Clone();
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.algoused = algoused;
+                _result.minibatchsize = minibatchsize;
+                _result.generator = (hqrnd.hqrndstate)generator.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Temporary data structures used by following functions:
+        * TrainEnsembleX
+
+        This structure contains:
+        * two arrays which can be used to store training and validation subsets
+        * sessions for MLP training
+
+        This structure should be initialized with InitMLPETrnSession() call.
+        *************************************************************************/
+        public class mlpetrnsession : apobject
+        {
+            public int[] trnsubset;
+            public int[] valsubset;
+            public alglib.smp.shared_pool mlpsessions;
+            public mlpreport mlprep;
+            public mlpbase.multilayerperceptron network;
+            public mlpetrnsession()
+            {
+                init();
+            }
+            public override void init()
+            {
+                trnsubset = new int[0];
+                valsubset = new int[0];
+                mlpsessions = new alglib.smp.shared_pool();
+                mlprep = new mlpreport();
+                network = new mlpbase.multilayerperceptron();
+            }
+            public override alglib.apobject make_copy()
+            {
+                mlpetrnsession _result = new mlpetrnsession();
+                _result.trnsubset = (int[])trnsubset.Clone();
+                _result.valsubset = (int[])valsubset.Clone();
+                _result.mlpsessions = (alglib.smp.shared_pool)mlpsessions.make_copy();
+                _result.mlprep = (mlpreport)mlprep.make_copy();
+                _result.network = (mlpbase.multilayerperceptron)network.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Trainer object for neural network.
+
+        You should not try to access fields of this object directly -  use  ALGLIB
+        functions to work with this object.
+        *************************************************************************/
+        public class mlptrainer : apobject
+        {
+            public int nin;
+            public int nout;
+            public bool rcpar;
+            public int lbfgsfactor;
+            public double decay;
+            public double wstep;
+            public int maxits;
+            public int datatype;
+            public int npoints;
+            public double[,] densexy;
+            public sparse.sparsematrix sparsexy;
+            public smlptrnsession session;
+            public int ngradbatch;
+            public int[] subset;
+            public int subsetsize;
+            public int[] valsubset;
+            public int valsubsetsize;
+            public int algokind;
+            public int minibatchsize;
+            public mlptrainer()
+            {
+                init();
+            }
+            public override void init()
+            {
+                densexy = new double[0,0];
+                sparsexy = new sparse.sparsematrix();
+                session = new smlptrnsession();
+                subset = new int[0];
+                valsubset = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                mlptrainer _result = new mlptrainer();
+                _result.nin = nin;
+                _result.nout = nout;
+                _result.rcpar = rcpar;
+                _result.lbfgsfactor = lbfgsfactor;
+                _result.decay = decay;
+                _result.wstep = wstep;
+                _result.maxits = maxits;
+                _result.datatype = datatype;
+                _result.npoints = npoints;
+                _result.densexy = (double[,])densexy.Clone();
+                _result.sparsexy = (sparse.sparsematrix)sparsexy.make_copy();
+                _result.session = (smlptrnsession)session.make_copy();
+                _result.ngradbatch = ngradbatch;
+                _result.subset = (int[])subset.Clone();
+                _result.subsetsize = subsetsize;
+                _result.valsubset = (int[])valsubset.Clone();
+                _result.valsubsetsize = valsubsetsize;
+                _result.algokind = algokind;
+                _result.minibatchsize = minibatchsize;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Internal record for parallelization function MLPFoldCV.
+        *************************************************************************/
+        public class mlpparallelizationcv : apobject
+        {
+            public mlpbase.multilayerperceptron network;
+            public mlpreport rep;
+            public int[] subset;
+            public int subsetsize;
+            public double[] xyrow;
+            public double[] y;
+            public int ngrad;
+            public alglib.smp.shared_pool trnpool;
+            public mlpparallelizationcv()
+            {
+                init();
+            }
+            public override void init()
+            {
+                network = new mlpbase.multilayerperceptron();
+                rep = new mlpreport();
+                subset = new int[0];
+                xyrow = new double[0];
+                y = new double[0];
+                trnpool = new alglib.smp.shared_pool();
+            }
+            public override alglib.apobject make_copy()
+            {
+                mlpparallelizationcv _result = new mlpparallelizationcv();
+                _result.network = (mlpbase.multilayerperceptron)network.make_copy();
+                _result.rep = (mlpreport)rep.make_copy();
+                _result.subset = (int[])subset.Clone();
+                _result.subsetsize = subsetsize;
+                _result.xyrow = (double[])xyrow.Clone();
+                _result.y = (double[])y.Clone();
+                _result.ngrad = ngrad;
+                _result.trnpool = (alglib.smp.shared_pool)trnpool.make_copy();
+                return _result;
+            }
+        };
+
+
+
+
+        public const double mindecay = 0.001;
+        public const int defaultlbfgsfactor = 6;
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int nin = 0;
+            int nout = 0;
+            int wcount = 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;
+            directdensesolvers.densesolverreport solverrep = new directdensesolvers.densesolverreport();
+            int i_ = 0;
+
+            info = 0;
+
+            mlpbase.mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            lambdaup = 10;
+            lambdadown = 0.3;
+            lmsteptol = 0.001;
+            
+            //
+            // Test for inputs
+            //
+            if( npoints<=0 || restarts<1 )
+            {
+                info = -1;
+                return;
+            }
+            if( mlpbase.mlpissoftmax(network, _params) )
+            {
+                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, _params);
+            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, _params);
+                
+                //
+                // 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, _params);
+                minlbfgs.minlbfgssetcond(state, 0, 0, 0, Math.Max(25, wcount), _params);
+                while( minlbfgs.minlbfgsiteration(state, _params) )
+                {
+                    
+                    //
+                    // 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, _params);
+                    
+                    //
+                    // 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, _params);
+                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, _params);
+                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, _params);
+                    rep.ncholesky = rep.ncholesky+1;
+                    if( !spd )
+                    {
+                        lambdav = lambdav*lambdaup*nu;
+                        nu = nu*2;
+                        continue;
+                    }
+                    directdensesolvers.spdmatrixcholeskysolve(hmod, wcount, true, g, ref solverinfo, solverrep, ref wdir, _params);
+                    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, _params)+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, _params);
+                    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, _params);
+                    minlbfgs.minlbfgssetcond(state, 0, 0, 0, 5, _params);
+                    while( minlbfgs.minlbfgsiteration(state, _params) )
+                    {
+                        
+                        //
+                        // 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, _params);
+                        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, _params);
+                    
+                    //
+                    // 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, _params);
+                    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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            if( mlpbase.mlpissoftmax(network, _params) )
+            {
+                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, _params);
+            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, _params);
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    w[i_] = network.weights[i_];
+                }
+                minlbfgs.minlbfgscreate(wcount, Math.Min(wcount, 10), w, state, _params);
+                minlbfgs.minlbfgssetcond(state, 0.0, 0.0, wstep, maxits, _params);
+                while( minlbfgs.minlbfgsiteration(state, _params) )
+                {
+                    for(i_=0; i_<=wcount-1;i_++)
+                    {
+                        network.weights[i_] = state.x[i_];
+                    }
+                    mlpbase.mlpgradnbatch(network, xy, npoints, ref state.f, ref state.g, _params);
+                    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, _params);
+                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, _params)+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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            if( mlpbase.mlpissoftmax(network, _params) )
+            {
+                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, _params);
+            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, _params);
+                }
+                ebest = mlpbase.mlperror(network, valxy, valsize, _params);
+                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, _params);
+                minlbfgs.minlbfgssetcond(state, 0.0, 0.0, wstep, 0, _params);
+                minlbfgs.minlbfgssetxrep(state, true, _params);
+                while( minlbfgs.minlbfgsiteration(state, _params) )
+                {
+                    
+                    //
+                    // 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, _params);
+                        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, _params);
+                        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, _params);
+                
+                //
+                // 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,
+            alglib.xparams _params)
+        {
+            info = 0;
+
+            mlpkfoldcvgeneral(network, xy, npoints, decay, restarts, foldscount, false, wstep, maxits, ref info, rep, cvrep, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            info = 0;
+
+            mlpkfoldcvgeneral(network, xy, npoints, decay, restarts, foldscount, true, 0.0, 0, ref info, rep, cvrep, _params);
+        }
+
+
+        /*************************************************************************
+        This function estimates generalization error using cross-validation on the
+        current dataset with current training settings.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            S           -   trainer object
+            Network     -   neural network. It must have same number of inputs and
+                            output/classes as was specified during creation of the
+                            trainer object. Network is not changed  during  cross-
+                            validation and is not trained - it  is  used  only  as
+                            representative of its architecture. I.e., we  estimate
+                            generalization properties of  ARCHITECTURE,  not  some
+                            specific network.
+            NRestarts   -   number of restarts, >=0:
+                            * NRestarts>0  means  that  for  each cross-validation
+                              round   specified  number   of  random  restarts  is
+                              performed,  with  best  network  being  chosen after
+                              training.
+                            * NRestarts=0 is same as NRestarts=1
+            FoldsCount  -   number of folds in k-fold cross-validation:
+                            * 2<=FoldsCount<=size of dataset
+                            * recommended value: 10.
+                            * values larger than dataset size will be silently
+                              truncated down to dataset size
+
+        OUTPUT PARAMETERS:
+            Rep         -   structure which contains cross-validation estimates:
+                            * Rep.RelCLSError - fraction of misclassified cases.
+                            * Rep.AvgCE - acerage cross-entropy
+                            * Rep.RMSError - root-mean-square error
+                            * Rep.AvgError - average error
+                            * Rep.AvgRelError - average relative error
+                            
+        NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(),
+              or subset with only one point  was  given,  zeros  are  returned  as
+              estimates.
+
+        NOTE: this method performs FoldsCount cross-validation  rounds,  each  one
+              with NRestarts random starts.  Thus,  FoldsCount*NRestarts  networks
+              are trained in total.
+
+        NOTE: Rep.RelCLSError/Rep.AvgCE are zero on regression problems.
+
+        NOTE: on classification problems Rep.RMSError/Rep.AvgError/Rep.AvgRelError
+              contain errors in prediction of posterior probabilities.
+                
+          -- ALGLIB --
+             Copyright 23.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpkfoldcv(mlptrainer s,
+            mlpbase.multilayerperceptron network,
+            int nrestarts,
+            int foldscount,
+            mlpreport rep,
+            alglib.xparams _params)
+        {
+            alglib.smp.shared_pool pooldatacv = new alglib.smp.shared_pool();
+            mlpparallelizationcv datacv = new mlpparallelizationcv();
+            mlpparallelizationcv sdatacv = null;
+            double[,] cvy = new double[0,0];
+            int[] folds = new int[0];
+            double[] buf = new double[0];
+            double[] dy = new double[0];
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int rowsize = 0;
+            int ntype = 0;
+            int ttype = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( !mlpbase.mlpissoftmax(network, _params) )
+            {
+                ntype = 0;
+            }
+            else
+            {
+                ntype = 1;
+            }
+            if( s.rcpar )
+            {
+                ttype = 0;
+            }
+            else
+            {
+                ttype = 1;
+            }
+            alglib.ap.assert(ntype==ttype, "MLPKFoldCV: type of input network is not similar to network type in trainer object");
+            alglib.ap.assert(s.npoints>=0, "MLPKFoldCV: possible trainer S is not initialized(S.NPoints<0)");
+            mlpbase.mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            alglib.ap.assert(s.nin==nin, "MLPKFoldCV:  number of inputs in trainer is not equal to number of inputs in network");
+            alglib.ap.assert(s.nout==nout, "MLPKFoldCV:  number of outputs in trainer is not equal to number of outputs in network");
+            alglib.ap.assert(nrestarts>=0, "MLPKFoldCV: NRestarts<0");
+            alglib.ap.assert(foldscount>=2, "MLPKFoldCV: FoldsCount<2");
+            if( foldscount>s.npoints )
+            {
+                foldscount = s.npoints;
+            }
+            rep.relclserror = 0;
+            rep.avgce = 0;
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.avgrelerror = 0;
+            hqrnd.hqrndrandomize(rs, _params);
+            rep.ngrad = 0;
+            rep.nhess = 0;
+            rep.ncholesky = 0;
+            if( s.npoints==0 || s.npoints==1 )
+            {
+                return;
+            }
+            
+            //
+            // Read network geometry, test parameters
+            //
+            if( s.rcpar )
+            {
+                rowsize = nin+nout;
+                dy = new double[nout];
+                bdss.dserrallocate(-nout, ref buf, _params);
+            }
+            else
+            {
+                rowsize = nin+1;
+                dy = new double[1];
+                bdss.dserrallocate(nout, ref buf, _params);
+            }
+            
+            //
+            // Folds
+            //
+            folds = new int[s.npoints];
+            for(i=0; i<=s.npoints-1; i++)
+            {
+                folds[i] = i*foldscount/s.npoints;
+            }
+            for(i=0; i<=s.npoints-2; i++)
+            {
+                j = i+hqrnd.hqrnduniformi(rs, s.npoints-i, _params);
+                if( j!=i )
+                {
+                    k = folds[i];
+                    folds[i] = folds[j];
+                    folds[j] = k;
+                }
+            }
+            cvy = new double[s.npoints, nout];
+            
+            //
+            // Initialize SEED-value for shared pool
+            //
+            datacv.ngrad = 0;
+            mlpbase.mlpcopy(network, datacv.network, _params);
+            datacv.subset = new int[s.npoints];
+            datacv.xyrow = new double[rowsize];
+            datacv.y = new double[nout];
+            
+            //
+            // Create shared pool
+            //
+            alglib.smp.ae_shared_pool_set_seed(pooldatacv, datacv);
+            
+            //
+            // Parallelization
+            //
+            mthreadcv(s, rowsize, nrestarts, folds, 0, foldscount, cvy, pooldatacv, wcount, _params);
+            
+            //
+            // Calculate value for NGrad
+            //
+            alglib.smp.ae_shared_pool_first_recycled(pooldatacv, ref sdatacv);
+            while( sdatacv!=null )
+            {
+                rep.ngrad = rep.ngrad+sdatacv.ngrad;
+                alglib.smp.ae_shared_pool_next_recycled(pooldatacv, ref sdatacv);
+            }
+            
+            //
+            // Connect of results and calculate cross-validation error
+            //
+            for(i=0; i<=s.npoints-1; i++)
+            {
+                if( s.datatype==0 )
+                {
+                    for(i_=0; i_<=rowsize-1;i_++)
+                    {
+                        datacv.xyrow[i_] = s.densexy[i,i_];
+                    }
+                }
+                if( s.datatype==1 )
+                {
+                    sparse.sparsegetrow(s.sparsexy, i, ref datacv.xyrow, _params);
+                }
+                for(i_=0; i_<=nout-1;i_++)
+                {
+                    datacv.y[i_] = cvy[i,i_];
+                }
+                if( s.rcpar )
+                {
+                    i1_ = (nin) - (0);
+                    for(i_=0; i_<=nout-1;i_++)
+                    {
+                        dy[i_] = datacv.xyrow[i_+i1_];
+                    }
+                }
+                else
+                {
+                    dy[0] = datacv.xyrow[nin];
+                }
+                bdss.dserraccumulate(ref buf, datacv.y, dy, _params);
+            }
+            bdss.dserrfinish(ref buf, _params);
+            rep.relclserror = buf[0];
+            rep.avgce = buf[1];
+            rep.rmserror = buf[2];
+            rep.avgerror = buf[3];
+            rep.avgrelerror = buf[4];
+        }
+
+
+        /*************************************************************************
+        Creation of the network trainer object for regression networks
+
+        INPUT PARAMETERS:
+            NIn         -   number of inputs, NIn>=1
+            NOut        -   number of outputs, NOut>=1
+
+        OUTPUT PARAMETERS:
+            S           -   neural network trainer object.
+                            This structure can be used to train any regression
+                            network with NIn inputs and NOut outputs.
+
+          -- ALGLIB --
+             Copyright 23.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreatetrainer(int nin,
+            int nout,
+            mlptrainer s,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(nin>=1, "MLPCreateTrainer: NIn<1.");
+            alglib.ap.assert(nout>=1, "MLPCreateTrainer: NOut<1.");
+            s.nin = nin;
+            s.nout = nout;
+            s.rcpar = true;
+            s.lbfgsfactor = defaultlbfgsfactor;
+            s.decay = 1.0E-6;
+            mlpsetcond(s, 0, 0, _params);
+            s.datatype = 0;
+            s.npoints = 0;
+            mlpsetalgobatch(s, _params);
+        }
+
+
+        /*************************************************************************
+        Creation of the network trainer object for classification networks
+
+        INPUT PARAMETERS:
+            NIn         -   number of inputs, NIn>=1
+            NClasses    -   number of classes, NClasses>=2
+
+        OUTPUT PARAMETERS:
+            S           -   neural network trainer object.
+                            This structure can be used to train any classification
+                            network with NIn inputs and NOut outputs.
+
+          -- ALGLIB --
+             Copyright 23.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpcreatetrainercls(int nin,
+            int nclasses,
+            mlptrainer s,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(nin>=1, "MLPCreateTrainerCls: NIn<1.");
+            alglib.ap.assert(nclasses>=2, "MLPCreateTrainerCls: NClasses<2.");
+            s.nin = nin;
+            s.nout = nclasses;
+            s.rcpar = false;
+            s.lbfgsfactor = defaultlbfgsfactor;
+            s.decay = 1.0E-6;
+            mlpsetcond(s, 0, 0, _params);
+            s.datatype = 0;
+            s.npoints = 0;
+            mlpsetalgobatch(s, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets "current dataset" of the trainer object to  one  passed
+        by user.
+
+        INPUT PARAMETERS:
+            S           -   trainer object
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format. This function checks  correctness
+                            of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                            correct) and throws exception when  incorrect  dataset
+                            is passed.
+            NPoints     -   points count, >=0.
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        datasetformat is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+          
+          -- ALGLIB --
+             Copyright 23.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpsetdataset(mlptrainer s,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            int ndim = 0;
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(s.nin>=1, "MLPSetDataset: possible parameter S is not initialized or spoiled(S.NIn<=0).");
+            alglib.ap.assert(npoints>=0, "MLPSetDataset: NPoint<0");
+            alglib.ap.assert(npoints<=alglib.ap.rows(xy), "MLPSetDataset: invalid size of matrix XY(NPoint more then rows of matrix XY)");
+            s.datatype = 0;
+            s.npoints = npoints;
+            if( npoints==0 )
+            {
+                return;
+            }
+            if( s.rcpar )
+            {
+                alglib.ap.assert(s.nout>=1, "MLPSetDataset: possible parameter S is not initialized or is spoiled(NOut<1 for regression).");
+                ndim = s.nin+s.nout;
+                alglib.ap.assert(ndim<=alglib.ap.cols(xy), "MLPSetDataset: invalid size of matrix XY(too few columns in matrix XY).");
+                alglib.ap.assert(apserv.apservisfinitematrix(xy, npoints, ndim, _params), "MLPSetDataset: parameter XY contains Infinite or NaN.");
+            }
+            else
+            {
+                alglib.ap.assert(s.nout>=2, "MLPSetDataset: possible parameter S is not initialized or is spoiled(NClasses<2 for classifier).");
+                ndim = s.nin+1;
+                alglib.ap.assert(ndim<=alglib.ap.cols(xy), "MLPSetDataset: invalid size of matrix XY(too few columns in matrix XY).");
+                alglib.ap.assert(apserv.apservisfinitematrix(xy, npoints, ndim, _params), "MLPSetDataset: parameter XY contains Infinite or NaN.");
+                for(i=0; i<=npoints-1; i++)
+                {
+                    alglib.ap.assert((int)Math.Round(xy[i,s.nin])>=0 && (int)Math.Round(xy[i,s.nin])<s.nout, "MLPSetDataset: invalid parameter XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).");
+                }
+            }
+            apserv.rmatrixsetlengthatleast(ref s.densexy, npoints, ndim, _params);
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=ndim-1; j++)
+                {
+                    s.densexy[i,j] = xy[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets "current dataset" of the trainer object to  one  passed
+        by user (sparse matrix is used to store dataset).
+
+        INPUT PARAMETERS:
+            S           -   trainer object
+            XY          -   training  set,  see  below  for  information  on   the
+                            training set format. This function checks  correctness
+                            of  the  dataset  (no  NANs/INFs,  class  numbers  are
+                            correct) and throws exception when  incorrect  dataset
+                            is passed. Any  sparse  storage  format  can be  used:
+                            Hash-table, CRS...
+            NPoints     -   points count, >=0
+
+        DATASET FORMAT:
+
+        This  function  uses  two  different  dataset formats - one for regression
+        networks, another one for classification networks.
+
+        For regression networks with NIn inputs and NOut outputs following dataset
+        format is used:
+        * dataset is given by NPoints*(NIn+NOut) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, next NOut columns are outputs
+
+        For classification networks with NIn inputs and NClasses clases  following
+        datasetformat is used:
+        * dataset is given by NPoints*(NIn+1) matrix
+        * each row corresponds to one example
+        * first NIn columns are inputs, last column stores class number (from 0 to
+          NClasses-1).
+          
+          -- ALGLIB --
+             Copyright 23.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpsetsparsedataset(mlptrainer s,
+            sparse.sparsematrix xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double v = 0;
+            int t0 = 0;
+            int t1 = 0;
+            int i = 0;
+            int j = 0;
+
+            
+            //
+            // Check correctness of the data
+            //
+            alglib.ap.assert(s.nin>0, "MLPSetSparseDataset: possible parameter S is not initialized or spoiled(S.NIn<=0).");
+            alglib.ap.assert(npoints>=0, "MLPSetSparseDataset: NPoint<0");
+            alglib.ap.assert(npoints<=sparse.sparsegetnrows(xy, _params), "MLPSetSparseDataset: invalid size of sparse matrix XY(NPoint more then rows of matrix XY)");
+            if( npoints>0 )
+            {
+                t0 = 0;
+                t1 = 0;
+                if( s.rcpar )
+                {
+                    alglib.ap.assert(s.nout>=1, "MLPSetSparseDataset: possible parameter S is not initialized or is spoiled(NOut<1 for regression).");
+                    alglib.ap.assert(s.nin+s.nout<=sparse.sparsegetncols(xy, _params), "MLPSetSparseDataset: invalid size of sparse matrix XY(too few columns in sparse matrix XY).");
+                    while( sparse.sparseenumerate(xy, ref t0, ref t1, ref i, ref j, ref v, _params) )
+                    {
+                        if( i<npoints && j<s.nin+s.nout )
+                        {
+                            alglib.ap.assert(math.isfinite(v), "MLPSetSparseDataset: sparse matrix XY contains Infinite or NaN.");
+                        }
+                    }
+                }
+                else
+                {
+                    alglib.ap.assert(s.nout>=2, "MLPSetSparseDataset: possible parameter S is not initialized or is spoiled(NClasses<2 for classifier).");
+                    alglib.ap.assert(s.nin+1<=sparse.sparsegetncols(xy, _params), "MLPSetSparseDataset: invalid size of sparse matrix XY(too few columns in sparse matrix XY).");
+                    while( sparse.sparseenumerate(xy, ref t0, ref t1, ref i, ref j, ref v, _params) )
+                    {
+                        if( i<npoints && j<=s.nin )
+                        {
+                            if( j!=s.nin )
+                            {
+                                alglib.ap.assert(math.isfinite(v), "MLPSetSparseDataset: sparse matrix XY contains Infinite or NaN.");
+                            }
+                            else
+                            {
+                                alglib.ap.assert((math.isfinite(v) && (int)Math.Round(v)>=0) && (int)Math.Round(v)<s.nout, "MLPSetSparseDataset: invalid sparse matrix XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses).");
+                            }
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Set dataset
+            //
+            s.datatype = 1;
+            s.npoints = npoints;
+            sparse.sparsecopytocrs(xy, s.sparsexy, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets weight decay coefficient which is used for training.
+
+        INPUT PARAMETERS:
+            S           -   trainer object
+            Decay       -   weight  decay  coefficient,  >=0.  Weight  decay  term
+                            'Decay*||Weights||^2' is added to error  function.  If
+                            you don't know what Decay to choose, use 1.0E-3.
+                            Weight decay can be set to zero,  in this case network
+                            is trained without weight decay.
+
+        NOTE: by default network uses some small nonzero value for weight decay.
+
+          -- ALGLIB --
+             Copyright 23.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpsetdecay(mlptrainer s,
+            double decay,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(decay), "MLPSetDecay: parameter Decay contains Infinite or NaN.");
+            alglib.ap.assert((double)(decay)>=(double)(0), "MLPSetDecay: Decay<0.");
+            s.decay = decay;
+        }
+
+
+        /*************************************************************************
+        This function sets stopping criteria for the optimizer.
+
+        INPUT PARAMETERS:
+            S           -   trainer object
+            WStep       -   stopping criterion. Algorithm stops if  step  size  is
+                            less than WStep. Recommended value - 0.01.  Zero  step
+                            size means stopping after MaxIts iterations.
+                            WStep>=0.
+            MaxIts      -   stopping   criterion.  Algorithm  stops  after  MaxIts
+                            epochs (full passes over entire dataset).  Zero MaxIts
+                            means stopping when step is sufficiently small.
+                            MaxIts>=0.
+
+        NOTE: by default, WStep=0.005 and MaxIts=0 are used. These values are also
+              used when MLPSetCond() is called with WStep=0 and MaxIts=0.
+              
+        NOTE: these stopping criteria are used for all kinds of neural training  -
+              from "conventional" networks to early stopping ensembles. When  used
+              for "conventional" networks, they are  used  as  the  only  stopping
+              criteria. When combined with early stopping, they used as ADDITIONAL
+              stopping criteria which can terminate early stopping algorithm.
+
+          -- ALGLIB --
+             Copyright 23.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpsetcond(mlptrainer s,
+            double wstep,
+            int maxits,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(wstep), "MLPSetCond: parameter WStep contains Infinite or NaN.");
+            alglib.ap.assert((double)(wstep)>=(double)(0), "MLPSetCond: WStep<0.");
+            alglib.ap.assert(maxits>=0, "MLPSetCond: MaxIts<0.");
+            if( (double)(wstep)!=(double)(0) || maxits!=0 )
+            {
+                s.wstep = wstep;
+                s.maxits = maxits;
+            }
+            else
+            {
+                s.wstep = 0.005;
+                s.maxits = 0;
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets training algorithm: batch training using L-BFGS will be
+        used.
+
+        This algorithm:
+        * the most robust for small-scale problems, but may be too slow for  large
+          scale ones.
+        * perfoms full pass through the dataset before performing step
+        * uses conditions specified by MLPSetCond() for stopping
+        * is default one used by trainer object
+
+        INPUT PARAMETERS:
+            S           -   trainer object
+
+          -- ALGLIB --
+             Copyright 23.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpsetalgobatch(mlptrainer s,
+            alglib.xparams _params)
+        {
+            s.algokind = 0;
+        }
+
+
+        /*************************************************************************
+        This function trains neural network passed to this function, using current
+        dataset (one which was passed to MLPSetDataset() or MLPSetSparseDataset())
+        and current training settings. Training  from  NRestarts  random  starting
+        positions is performed, best network is chosen.
+
+        Training is performed using current training algorithm.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            S           -   trainer object
+            Network     -   neural network. It must have same number of inputs and
+                            output/classes as was specified during creation of the
+                            trainer object.
+            NRestarts   -   number of restarts, >=0:
+                            * NRestarts>0 means that specified  number  of  random
+                              restarts are performed, best network is chosen after
+                              training
+                            * NRestarts=0 means that current state of the  network
+                              is used for training.
+
+        OUTPUT PARAMETERS:
+            Network     -   trained network
+
+        NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(),
+              network  is  filled  by zero  values.  Same  behavior  for functions
+              MLPStartTraining and MLPContinueTraining.
+
+        NOTE: this method uses sum-of-squares error function for training.
+
+          -- ALGLIB --
+             Copyright 23.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlptrainnetwork(mlptrainer s,
+            mlpbase.multilayerperceptron network,
+            int nrestarts,
+            mlpreport rep,
+            alglib.xparams _params)
+        {
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int ntype = 0;
+            int ttype = 0;
+            alglib.smp.shared_pool trnpool = new alglib.smp.shared_pool();
+
+            alglib.ap.assert(s.npoints>=0, "MLPTrainNetwork: parameter S is not initialized or is spoiled(S.NPoints<0)");
+            if( !mlpbase.mlpissoftmax(network, _params) )
+            {
+                ntype = 0;
+            }
+            else
+            {
+                ntype = 1;
+            }
+            if( s.rcpar )
+            {
+                ttype = 0;
+            }
+            else
+            {
+                ttype = 1;
+            }
+            alglib.ap.assert(ntype==ttype, "MLPTrainNetwork: type of input network is not similar to network type in trainer object");
+            mlpbase.mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            alglib.ap.assert(s.nin==nin, "MLPTrainNetwork: number of inputs in trainer is not equal to number of inputs in network");
+            alglib.ap.assert(s.nout==nout, "MLPTrainNetwork: number of outputs in trainer is not equal to number of outputs in network");
+            alglib.ap.assert(nrestarts>=0, "MLPTrainNetwork: NRestarts<0.");
+            
+            //
+            // Train
+            //
+            mlptrainnetworkx(s, nrestarts, -1, s.subset, -1, s.subset, 0, network, rep, true, trnpool, _params);
+        }
+
+
+        /*************************************************************************
+        IMPORTANT: this is an "expert" version of the MLPTrain() function.  We  do
+                   not recommend you to use it unless you are pretty sure that you
+                   need ability to monitor training progress.
+
+        This function performs step-by-step training of the neural  network.  Here
+        "step-by-step" means that training  starts  with  MLPStartTraining() call,
+        and then user subsequently calls MLPContinueTraining() to perform one more
+        iteration of the training.
+
+        After call to this function trainer object remembers network and  is ready
+        to  train  it.  However,  no  training  is  performed  until first call to 
+        MLPContinueTraining() function. Subsequent calls  to MLPContinueTraining()
+        will advance training progress one iteration further.
+
+        EXAMPLE:
+            >
+            > ...initialize network and trainer object....
+            >
+            > MLPStartTraining(Trainer, Network, True)
+            > while MLPContinueTraining(Trainer, Network) do
+            >     ...visualize training progress...
+            >
+
+        INPUT PARAMETERS:
+            S           -   trainer object
+            Network     -   neural network. It must have same number of inputs and
+                            output/classes as was specified during creation of the
+                            trainer object.
+            RandomStart -   randomize network before training or not:
+                            * True  means  that  network  is  randomized  and  its
+                              initial state (one which was passed to  the  trainer
+                              object) is lost.
+                            * False  means  that  training  is  started  from  the
+                              current state of the network
+                            
+        OUTPUT PARAMETERS:
+            Network     -   neural network which is ready to training (weights are
+                            initialized, preprocessor is initialized using current
+                            training set)
+
+        NOTE: this method uses sum-of-squares error function for training.
+
+        NOTE: it is expected that trainer object settings are NOT  changed  during
+              step-by-step training, i.e. no  one  changes  stopping  criteria  or
+              training set during training. It is possible and there is no defense
+              against  such  actions,  but  algorithm  behavior  in  such cases is
+              undefined and can be unpredictable.
+
+          -- ALGLIB --
+             Copyright 23.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlpstarttraining(mlptrainer s,
+            mlpbase.multilayerperceptron network,
+            bool randomstart,
+            alglib.xparams _params)
+        {
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int ntype = 0;
+            int ttype = 0;
+
+            alglib.ap.assert(s.npoints>=0, "MLPStartTraining: parameter S is not initialized or is spoiled(S.NPoints<0)");
+            if( !mlpbase.mlpissoftmax(network, _params) )
+            {
+                ntype = 0;
+            }
+            else
+            {
+                ntype = 1;
+            }
+            if( s.rcpar )
+            {
+                ttype = 0;
+            }
+            else
+            {
+                ttype = 1;
+            }
+            alglib.ap.assert(ntype==ttype, "MLPStartTraining: type of input network is not similar to network type in trainer object");
+            mlpbase.mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            alglib.ap.assert(s.nin==nin, "MLPStartTraining: number of inputs in trainer is not equal to number of inputs in the network.");
+            alglib.ap.assert(s.nout==nout, "MLPStartTraining: number of outputs in trainer is not equal to number of outputs in the network.");
+            
+            //
+            // Initialize temporaries
+            //
+            initmlptrnsession(network, randomstart, s, s.session, _params);
+            
+            //
+            // Train network
+            //
+            mlpstarttrainingx(s, randomstart, -1, s.subset, -1, s.session, _params);
+            
+            //
+            // Update network
+            //
+            mlpbase.mlpcopytunableparameters(s.session.network, network, _params);
+        }
+
+
+        /*************************************************************************
+        IMPORTANT: this is an "expert" version of the MLPTrain() function.  We  do
+                   not recommend you to use it unless you are pretty sure that you
+                   need ability to monitor training progress.
+                   
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        This function performs step-by-step training of the neural  network.  Here
+        "step-by-step" means that training starts  with  MLPStartTraining()  call,
+        and then user subsequently calls MLPContinueTraining() to perform one more
+        iteration of the training.
+
+        This  function  performs  one  more  iteration of the training and returns
+        either True (training continues) or False (training stopped). In case True
+        was returned, Network weights are updated according to the  current  state
+        of the optimization progress. In case False was  returned,  no  additional
+        updates is performed (previous update of  the  network weights moved us to
+        the final point, and no additional updates is needed).
+
+        EXAMPLE:
+            >
+            > [initialize network and trainer object]
+            >
+            > MLPStartTraining(Trainer, Network, True)
+            > while MLPContinueTraining(Trainer, Network) do
+            >     [visualize training progress]
+            >
+
+        INPUT PARAMETERS:
+            S           -   trainer object
+            Network     -   neural  network  structure,  which  is  used to  store
+                            current state of the training process.
+                            
+        OUTPUT PARAMETERS:
+            Network     -   weights of the neural network  are  rewritten  by  the
+                            current approximation.
+
+        NOTE: this method uses sum-of-squares error function for training.
+
+        NOTE: it is expected that trainer object settings are NOT  changed  during
+              step-by-step training, i.e. no  one  changes  stopping  criteria  or
+              training set during training. It is possible and there is no defense
+              against  such  actions,  but  algorithm  behavior  in  such cases is
+              undefined and can be unpredictable.
+              
+        NOTE: It  is  expected that Network is the same one which  was  passed  to
+              MLPStartTraining() function.  However,  THIS  function  checks  only
+              following:
+              * that number of network inputs is consistent with trainer object
+                settings
+              * that number of network outputs/classes is consistent with  trainer
+                object settings
+              * that number of network weights is the same as number of weights in
+                the network passed to MLPStartTraining() function
+              Exception is thrown when these conditions are violated.
+              
+              It is also expected that you do not change state of the  network  on
+              your own - the only party who has right to change network during its
+              training is a trainer object. Any attempt to interfere with  trainer
+              may lead to unpredictable results.
+              
+
+          -- ALGLIB --
+             Copyright 23.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool mlpcontinuetraining(mlptrainer s,
+            mlpbase.multilayerperceptron network,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int ntype = 0;
+            int ttype = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(s.npoints>=0, "MLPContinueTraining: parameter S is not initialized or is spoiled(S.NPoints<0)");
+            if( s.rcpar )
+            {
+                ttype = 0;
+            }
+            else
+            {
+                ttype = 1;
+            }
+            if( !mlpbase.mlpissoftmax(network, _params) )
+            {
+                ntype = 0;
+            }
+            else
+            {
+                ntype = 1;
+            }
+            alglib.ap.assert(ntype==ttype, "MLPContinueTraining: type of input network is not similar to network type in trainer object.");
+            mlpbase.mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            alglib.ap.assert(s.nin==nin, "MLPContinueTraining: number of inputs in trainer is not equal to number of inputs in the network.");
+            alglib.ap.assert(s.nout==nout, "MLPContinueTraining: number of outputs in trainer is not equal to number of outputs in the network.");
+            result = mlpcontinuetrainingx(s, s.subset, -1, ref s.ngradbatch, s.session, _params);
+            if( result )
+            {
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    network.weights[i_] = s.session.network.weights[i_];
+                }
+            }
+            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(mlpe.mlpensemble ensemble,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            ref int info,
+            mlpreport rep,
+            mlpcvreport ooberrors,
+            alglib.xparams _params)
+        {
+            info = 0;
+
+            mlpebagginginternal(ensemble, xy, npoints, decay, restarts, 0.0, 0, true, ref info, rep, ooberrors, _params);
+        }
+
+
+        /*************************************************************************
+        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(mlpe.mlpensemble ensemble,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            double wstep,
+            int maxits,
+            ref int info,
+            mlpreport rep,
+            mlpcvreport ooberrors,
+            alglib.xparams _params)
+        {
+            info = 0;
+
+            mlpebagginginternal(ensemble, xy, npoints, decay, restarts, wstep, maxits, false, ref info, rep, ooberrors, _params);
+        }
+
+
+        /*************************************************************************
+        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(mlpe.mlpensemble ensemble,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            ref int info,
+            mlpreport rep,
+            alglib.xparams _params)
+        {
+            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;
+            mlpreport tmprep = new mlpreport();
+            mlpbase.modelerrors moderr = new mlpbase.modelerrors();
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            info = 0;
+
+            nin = mlpbase.mlpgetinputscount(ensemble.network, _params);
+            nout = mlpbase.mlpgetoutputscount(ensemble.network, _params);
+            wcount = mlpbase.mlpgetweightscount(ensemble.network, _params);
+            if( (npoints<2 || restarts<1) || (double)(decay)<(double)(0) )
+            {
+                info = -1;
+                return;
+            }
+            if( mlpbase.mlpissoftmax(ensemble.network, _params) )
+            {
+                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, _params) )
+            {
+                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
+                //
+                mlptraines(ensemble.network, trnxy, trnsize, valxy, valsize, decay, restarts, ref tmpinfo, tmprep, _params);
+                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;
+            }
+            mlpe.mlpeallerrorsx(ensemble, xy, ensemble.network.dummysxy, npoints, 0, ensemble.network.dummyidx, 0, npoints, 0, ensemble.network.buf, moderr, _params);
+            rep.relclserror = moderr.relclserror;
+            rep.avgce = moderr.avgce;
+            rep.rmserror = moderr.rmserror;
+            rep.avgerror = moderr.avgerror;
+            rep.avgrelerror = moderr.avgrelerror;
+        }
+
+
+        /*************************************************************************
+        This function trains neural network ensemble passed to this function using
+        current dataset and early stopping training algorithm. Each early stopping
+        round performs NRestarts  random  restarts  (thus,  EnsembleSize*NRestarts
+        training rounds is performed in total).
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            S           -   trainer object;
+            Ensemble    -   neural network ensemble. It must have same  number  of
+                            inputs and outputs/classes  as  was  specified  during
+                            creation of the trainer object.
+            NRestarts   -   number of restarts, >=0:
+                            * NRestarts>0 means that specified  number  of  random
+                              restarts are performed during each ES round;
+                            * NRestarts=0 is silently replaced by 1.
+
+        OUTPUT PARAMETERS:
+            Ensemble    -   trained ensemble;
+            Rep         -   it contains all type of errors.
+            
+        NOTE: this training method uses BOTH early stopping and weight decay!  So,
+              you should select weight decay before starting training just as  you
+              select it before training "conventional" networks.
+
+        NOTE: when no dataset was specified with MLPSetDataset/SetSparseDataset(),
+              or  single-point  dataset  was  passed,  ensemble  is filled by zero
+              values.
+
+        NOTE: this method uses sum-of-squares error function for training.
+
+          -- ALGLIB --
+             Copyright 22.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mlptrainensemblees(mlptrainer s,
+            mlpe.mlpensemble ensemble,
+            int nrestarts,
+            mlpreport rep,
+            alglib.xparams _params)
+        {
+            int nin = 0;
+            int nout = 0;
+            int ntype = 0;
+            int ttype = 0;
+            alglib.smp.shared_pool esessions = new alglib.smp.shared_pool();
+            apserv.sinteger sgrad = new apserv.sinteger();
+            mlpbase.modelerrors tmprep = new mlpbase.modelerrors();
+
+            alglib.ap.assert(s.npoints>=0, "MLPTrainEnsembleES: parameter S is not initialized or is spoiled(S.NPoints<0)");
+            if( !mlpe.mlpeissoftmax(ensemble, _params) )
+            {
+                ntype = 0;
+            }
+            else
+            {
+                ntype = 1;
+            }
+            if( s.rcpar )
+            {
+                ttype = 0;
+            }
+            else
+            {
+                ttype = 1;
+            }
+            alglib.ap.assert(ntype==ttype, "MLPTrainEnsembleES: internal error - type of input network is not similar to network type in trainer object");
+            nin = mlpbase.mlpgetinputscount(ensemble.network, _params);
+            alglib.ap.assert(s.nin==nin, "MLPTrainEnsembleES: number of inputs in trainer is not equal to number of inputs in ensemble network");
+            nout = mlpbase.mlpgetoutputscount(ensemble.network, _params);
+            alglib.ap.assert(s.nout==nout, "MLPTrainEnsembleES: number of outputs in trainer is not equal to number of outputs in ensemble network");
+            alglib.ap.assert(nrestarts>=0, "MLPTrainEnsembleES: NRestarts<0.");
+            
+            //
+            // Initialize parameter Rep
+            //
+            rep.relclserror = 0;
+            rep.avgce = 0;
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.avgrelerror = 0;
+            rep.ngrad = 0;
+            rep.nhess = 0;
+            rep.ncholesky = 0;
+            
+            //
+            // Allocate
+            //
+            apserv.ivectorsetlengthatleast(ref s.subset, s.npoints, _params);
+            apserv.ivectorsetlengthatleast(ref s.valsubset, s.npoints, _params);
+            
+            //
+            // Start training
+            //
+            // NOTE: ESessions is not initialized because MLPTrainEnsembleX
+            //       needs uninitialized pool.
+            //
+            sgrad.val = 0;
+            mlptrainensemblex(s, ensemble, 0, ensemble.ensemblesize, nrestarts, 0, sgrad, true, esessions, _params);
+            rep.ngrad = sgrad.val;
+            
+            //
+            // Calculate errors.
+            //
+            if( s.datatype==0 )
+            {
+                mlpe.mlpeallerrorsx(ensemble, s.densexy, s.sparsexy, s.npoints, 0, ensemble.network.dummyidx, 0, s.npoints, 0, ensemble.network.buf, tmprep, _params);
+            }
+            if( s.datatype==1 )
+            {
+                mlpe.mlpeallerrorsx(ensemble, s.densexy, s.sparsexy, s.npoints, 1, ensemble.network.dummyidx, 0, s.npoints, 0, ensemble.network.buf, tmprep, _params);
+            }
+            rep.relclserror = tmprep.relclserror;
+            rep.avgce = tmprep.avgce;
+            rep.rmserror = tmprep.rmserror;
+            rep.avgerror = tmprep.avgerror;
+            rep.avgrelerror = tmprep.avgrelerror;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            if( mlpbase.mlpissoftmax(n, _params) )
+            {
+                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, _params);
+            
+            //
+            // 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, _params);
+            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, _params);
+                }
+                else
+                {
+                    mlptrainlbfgs(network, cvset, cvssize, decay, restarts, wstep, maxits, ref info, internalrep, _params);
+                }
+                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, _params) )
+                {
+                    
+                    //
+                    // classification-only code
+                    //
+                    cvrep.relclserror = cvrep.relclserror+mlpbase.mlpclserror(network, testset, tssize, _params);
+                    cvrep.avgce = cvrep.avgce+mlpbase.mlperrorn(network, testset, tssize, _params);
+                }
+                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, _params);
+                    if( mlpbase.mlpissoftmax(network, _params) )
+                    {
+                        
+                        //
+                        // 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, _params) )
+            {
+                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);
+            if( relcnt>0 )
+            {
+                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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+
+            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
+            //
+            hqrnd.hqrndrandomize(rs, _params);
+            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+hqrnd.hqrnduniformi(rs, npoints-i, _params);
+                if( j!=i )
+                {
+                    k = folds[i];
+                    folds[i] = folds[j];
+                    folds[j] = k;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine for parallelization function MLPFoldCV.
+
+
+        INPUT PARAMETERS:
+            S         -   trainer object;
+            RowSize   -   row size(eitherNIn+NOut or NIn+1);
+            NRestarts -   number of restarts(>=0);
+            Folds     -   cross-validation set;
+            Fold      -   the number of first cross-validation(>=0);
+            DFold     -   the number of second cross-validation(>=Fold+1);
+            CVY       -   parameter which stores  the result is returned by network,
+                          training on I-th cross-validation set.
+                          It has to be preallocated.
+            PoolDataCV-   parameter for parallelization.
+            WCount    -   number of weights in network, used to make decisions on
+                          parallelization.
+            
+        NOTE: There are no checks on the parameters correctness.
+
+          -- ALGLIB --
+             Copyright 25.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        private static void mthreadcv(mlptrainer s,
+            int rowsize,
+            int nrestarts,
+            int[] folds,
+            int fold,
+            int dfold,
+            double[,] cvy,
+            alglib.smp.shared_pool pooldatacv,
+            int wcount,
+            alglib.xparams _params)
+        {
+            mlpparallelizationcv datacv = null;
+            int i = 0;
+            int i_ = 0;
+
+            if( fold==dfold-1 )
+            {
+                
+                //
+                // Separate set
+                //
+                alglib.smp.ae_shared_pool_retrieve(pooldatacv, ref datacv);
+                datacv.subsetsize = 0;
+                for(i=0; i<=s.npoints-1; i++)
+                {
+                    if( folds[i]!=fold )
+                    {
+                        datacv.subset[datacv.subsetsize] = i;
+                        datacv.subsetsize = datacv.subsetsize+1;
+                    }
+                }
+                
+                //
+                // Train on CV training set
+                //
+                mlptrainnetworkx(s, nrestarts, -1, datacv.subset, datacv.subsetsize, datacv.subset, 0, datacv.network, datacv.rep, true, datacv.trnpool, _params);
+                datacv.ngrad = datacv.ngrad+datacv.rep.ngrad;
+                
+                //
+                // Estimate error using CV test set
+                //
+                for(i=0; i<=s.npoints-1; i++)
+                {
+                    if( folds[i]==fold )
+                    {
+                        if( s.datatype==0 )
+                        {
+                            for(i_=0; i_<=rowsize-1;i_++)
+                            {
+                                datacv.xyrow[i_] = s.densexy[i,i_];
+                            }
+                        }
+                        if( s.datatype==1 )
+                        {
+                            sparse.sparsegetrow(s.sparsexy, i, ref datacv.xyrow, _params);
+                        }
+                        mlpbase.mlpprocess(datacv.network, datacv.xyrow, ref datacv.y, _params);
+                        for(i_=0; i_<=s.nout-1;i_++)
+                        {
+                            cvy[i,i_] = datacv.y[i_];
+                        }
+                    }
+                }
+                alglib.smp.ae_shared_pool_recycle(pooldatacv, ref datacv);
+            }
+            else
+            {
+                alglib.ap.assert(fold<dfold-1, "MThreadCV: internal error(Fold>DFold-1).");
+                
+                //
+                // We expect that minimum number of iterations before convergence is 100.
+                // Hence is our approach to evaluation of task complexity.
+                //
+                if( (double)(Math.Max(nrestarts, 1)*apserv.rmul3(2*wcount, s.npoints, 100, _params))>=(double)(apserv.smpactivationlevel(_params)) )
+                {
+                    if( _trypexec_mthreadcv(s,rowsize,nrestarts,folds,fold,dfold,cvy,pooldatacv,wcount, _params) )
+                    {
+                        return;
+                    }
+                }
+                
+                //
+                // Split task
+                //
+                mthreadcv(s, rowsize, nrestarts, folds, fold, (fold+dfold)/2, cvy, pooldatacv, wcount, _params);
+                mthreadcv(s, rowsize, nrestarts, folds, (fold+dfold)/2, dfold, cvy, pooldatacv, wcount, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_mthreadcv(mlptrainer s,
+            int rowsize,
+            int nrestarts,
+            int[] folds,
+            int fold,
+            int dfold,
+            double[,] cvy,
+            alglib.smp.shared_pool pooldatacv,
+            int wcount, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        This function trains neural network passed to this function, using current
+        dataset (one which was passed to MLPSetDataset() or MLPSetSparseDataset())
+        and current training settings. Training  from  NRestarts  random  starting
+        positions is performed, best network is chosen.
+
+        This function is inteded to be used internally. It may be used in  several
+        settings:
+        * training with ValSubsetSize=0, corresponds  to  "normal"  training  with
+          termination  criteria  based on S.MaxIts (steps count) and S.WStep (step
+          size). Training sample is given by TrnSubset/TrnSubsetSize.
+        * training with ValSubsetSize>0, corresponds to  early  stopping  training
+          with additional MaxIts/WStep stopping criteria. Training sample is given
+          by TrnSubset/TrnSubsetSize, validation sample  is  given  by  ValSubset/
+          ValSubsetSize.
+
+          -- ALGLIB --
+             Copyright 13.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        private static void mlptrainnetworkx(mlptrainer s,
+            int nrestarts,
+            int algokind,
+            int[] trnsubset,
+            int trnsubsetsize,
+            int[] valsubset,
+            int valsubsetsize,
+            mlpbase.multilayerperceptron network,
+            mlpreport rep,
+            bool isrootcall,
+            alglib.smp.shared_pool sessions,
+            alglib.xparams _params)
+        {
+            mlpbase.modelerrors modrep = new mlpbase.modelerrors();
+            double eval = 0;
+            double ebest = 0;
+            int ngradbatch = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int pcount = 0;
+            int itbest = 0;
+            int itcnt = 0;
+            int ntype = 0;
+            int ttype = 0;
+            bool rndstart = new bool();
+            int i = 0;
+            int nr0 = 0;
+            int nr1 = 0;
+            mlpreport rep0 = new mlpreport();
+            mlpreport rep1 = new mlpreport();
+            bool randomizenetwork = new bool();
+            double bestrmserror = 0;
+            smlptrnsession psession = null;
+            int i_ = 0;
+
+            mlpbase.mlpproperties(network, ref nin, ref nout, ref wcount, _params);
+            
+            //
+            // Process root call
+            //
+            if( isrootcall )
+            {
+                
+                //
+                // Try parallelization
+                // We expect that minimum number of iterations before convergence is 100.
+                // Hence is our approach to evaluation of task complexity.
+                //
+                if( (double)(Math.Max(nrestarts, 1)*apserv.rmul3(2*wcount, s.npoints, 100, _params))>=(double)(apserv.smpactivationlevel(_params)) )
+                {
+                    if( _trypexec_mlptrainnetworkx(s,nrestarts,algokind,trnsubset,trnsubsetsize,valsubset,valsubsetsize,network,rep,isrootcall,sessions, _params) )
+                    {
+                        return;
+                    }
+                }
+                
+                //
+                // Check correctness of parameters
+                //
+                alglib.ap.assert(algokind==0 || algokind==-1, "MLPTrainNetworkX: unexpected AlgoKind");
+                alglib.ap.assert(s.npoints>=0, "MLPTrainNetworkX: internal error - parameter S is not initialized or is spoiled(S.NPoints<0)");
+                if( s.rcpar )
+                {
+                    ttype = 0;
+                }
+                else
+                {
+                    ttype = 1;
+                }
+                if( !mlpbase.mlpissoftmax(network, _params) )
+                {
+                    ntype = 0;
+                }
+                else
+                {
+                    ntype = 1;
+                }
+                alglib.ap.assert(ntype==ttype, "MLPTrainNetworkX: internal error - type of the training network is not similar to network type in trainer object");
+                alglib.ap.assert(s.nin==nin, "MLPTrainNetworkX: internal error - number of inputs in trainer is not equal to number of inputs in the training network.");
+                alglib.ap.assert(s.nout==nout, "MLPTrainNetworkX: internal error - number of outputs in trainer is not equal to number of outputs in the training network.");
+                alglib.ap.assert(nrestarts>=0, "MLPTrainNetworkX: internal error - NRestarts<0.");
+                alglib.ap.assert(alglib.ap.len(trnsubset)>=trnsubsetsize, "MLPTrainNetworkX: internal error - parameter TrnSubsetSize more than input subset size(Length(TrnSubset)<TrnSubsetSize)");
+                for(i=0; i<=trnsubsetsize-1; i++)
+                {
+                    alglib.ap.assert(trnsubset[i]>=0 && trnsubset[i]<=s.npoints-1, "MLPTrainNetworkX: internal error - parameter TrnSubset contains incorrect index(TrnSubset[I]<0 or TrnSubset[I]>S.NPoints-1)");
+                }
+                alglib.ap.assert(alglib.ap.len(valsubset)>=valsubsetsize, "MLPTrainNetworkX: internal error - parameter ValSubsetSize more than input subset size(Length(ValSubset)<ValSubsetSize)");
+                for(i=0; i<=valsubsetsize-1; i++)
+                {
+                    alglib.ap.assert(valsubset[i]>=0 && valsubset[i]<=s.npoints-1, "MLPTrainNetworkX: internal error - parameter ValSubset contains incorrect index(ValSubset[I]<0 or ValSubset[I]>S.NPoints-1)");
+                }
+                
+                //
+                // Train
+                //
+                randomizenetwork = nrestarts>0;
+                initmlptrnsessions(network, randomizenetwork, s, sessions, _params);
+                mlptrainnetworkx(s, nrestarts, algokind, trnsubset, trnsubsetsize, valsubset, valsubsetsize, network, rep, false, sessions, _params);
+                
+                //
+                // Choose best network
+                //
+                bestrmserror = math.maxrealnumber;
+                alglib.smp.ae_shared_pool_first_recycled(sessions, ref psession);
+                while( psession!=null )
+                {
+                    if( (double)(psession.bestrmserror)<(double)(bestrmserror) )
+                    {
+                        mlpbase.mlpimporttunableparameters(network, psession.bestparameters, _params);
+                        bestrmserror = psession.bestrmserror;
+                    }
+                    alglib.smp.ae_shared_pool_next_recycled(sessions, ref psession);
+                }
+                
+                //
+                // Calculate errors
+                //
+                if( s.datatype==0 )
+                {
+                    mlpbase.mlpallerrorssubset(network, s.densexy, s.npoints, trnsubset, trnsubsetsize, modrep, _params);
+                }
+                if( s.datatype==1 )
+                {
+                    mlpbase.mlpallerrorssparsesubset(network, s.sparsexy, s.npoints, trnsubset, trnsubsetsize, modrep, _params);
+                }
+                rep.relclserror = modrep.relclserror;
+                rep.avgce = modrep.avgce;
+                rep.rmserror = modrep.rmserror;
+                rep.avgerror = modrep.avgerror;
+                rep.avgrelerror = modrep.avgrelerror;
+                
+                //
+                // Done
+                //
+                return;
+            }
+            
+            //
+            // Split problem, if we have more than 1 restart
+            //
+            if( nrestarts>=2 )
+            {
+                
+                //
+                // Divide problem with NRestarts into two: NR0 and NR1.
+                //
+                nr0 = nrestarts/2;
+                nr1 = nrestarts-nr0;
+                mlptrainnetworkx(s, nr0, algokind, trnsubset, trnsubsetsize, valsubset, valsubsetsize, network, rep0, false, sessions, _params);
+                mlptrainnetworkx(s, nr1, algokind, trnsubset, trnsubsetsize, valsubset, valsubsetsize, network, rep1, false, sessions, _params);
+                
+                //
+                // Aggregate results
+                //
+                rep.ngrad = rep0.ngrad+rep1.ngrad;
+                rep.nhess = rep0.nhess+rep1.nhess;
+                rep.ncholesky = rep0.ncholesky+rep1.ncholesky;
+                
+                //
+                // Done :)
+                //
+                return;
+            }
+            
+            //
+            // Execution with NRestarts=1 or NRestarts=0:
+            // * NRestarts=1 means that network is restarted from random position
+            // * NRestarts=0 means that network is not randomized
+            //
+            alglib.ap.assert(nrestarts==0 || nrestarts==1, "MLPTrainNetworkX: internal error");
+            rep.ngrad = 0;
+            rep.nhess = 0;
+            rep.ncholesky = 0;
+            alglib.smp.ae_shared_pool_retrieve(sessions, ref psession);
+            if( ((s.datatype==0 || s.datatype==1) && s.npoints>0) && trnsubsetsize!=0 )
+            {
+                
+                //
+                // Train network using combination of early stopping and step-size
+                // and step-count based criteria. Network state with best value of
+                // validation set error is stored in WBuf0. When validation set is
+                // zero, most recent state of network is stored.
+                //
+                rndstart = nrestarts!=0;
+                ngradbatch = 0;
+                eval = 0;
+                ebest = 0;
+                itbest = 0;
+                itcnt = 0;
+                mlpstarttrainingx(s, rndstart, algokind, trnsubset, trnsubsetsize, psession, _params);
+                if( s.datatype==0 )
+                {
+                    ebest = mlpbase.mlperrorsubset(psession.network, s.densexy, s.npoints, valsubset, valsubsetsize, _params);
+                }
+                if( s.datatype==1 )
+                {
+                    ebest = mlpbase.mlperrorsparsesubset(psession.network, s.sparsexy, s.npoints, valsubset, valsubsetsize, _params);
+                }
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    psession.wbuf0[i_] = psession.network.weights[i_];
+                }
+                while( mlpcontinuetrainingx(s, trnsubset, trnsubsetsize, ref ngradbatch, psession, _params) )
+                {
+                    if( s.datatype==0 )
+                    {
+                        eval = mlpbase.mlperrorsubset(psession.network, s.densexy, s.npoints, valsubset, valsubsetsize, _params);
+                    }
+                    if( s.datatype==1 )
+                    {
+                        eval = mlpbase.mlperrorsparsesubset(psession.network, s.sparsexy, s.npoints, valsubset, valsubsetsize, _params);
+                    }
+                    if( (double)(eval)<=(double)(ebest) || valsubsetsize==0 )
+                    {
+                        for(i_=0; i_<=wcount-1;i_++)
+                        {
+                            psession.wbuf0[i_] = psession.network.weights[i_];
+                        }
+                        ebest = eval;
+                        itbest = itcnt;
+                    }
+                    if( itcnt>30 && (double)(itcnt)>(double)(1.5*itbest) )
+                    {
+                        break;
+                    }
+                    itcnt = itcnt+1;
+                }
+                for(i_=0; i_<=wcount-1;i_++)
+                {
+                    psession.network.weights[i_] = psession.wbuf0[i_];
+                }
+                rep.ngrad = ngradbatch;
+            }
+            else
+            {
+                for(i=0; i<=wcount-1; i++)
+                {
+                    psession.network.weights[i] = 0;
+                }
+            }
+            
+            //
+            // Evaluate network performance and update PSession.BestParameters/BestRMSError
+            // (if needed).
+            //
+            if( s.datatype==0 )
+            {
+                mlpbase.mlpallerrorssubset(psession.network, s.densexy, s.npoints, trnsubset, trnsubsetsize, modrep, _params);
+            }
+            if( s.datatype==1 )
+            {
+                mlpbase.mlpallerrorssparsesubset(psession.network, s.sparsexy, s.npoints, trnsubset, trnsubsetsize, modrep, _params);
+            }
+            if( (double)(modrep.rmserror)<(double)(psession.bestrmserror) )
+            {
+                mlpbase.mlpexporttunableparameters(psession.network, ref psession.bestparameters, ref pcount, _params);
+                psession.bestrmserror = modrep.rmserror;
+            }
+            
+            //
+            // Move session back to pool
+            //
+            alglib.smp.ae_shared_pool_recycle(sessions, ref psession);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_mlptrainnetworkx(mlptrainer s,
+            int nrestarts,
+            int algokind,
+            int[] trnsubset,
+            int trnsubsetsize,
+            int[] valsubset,
+            int valsubsetsize,
+            mlpbase.multilayerperceptron network,
+            mlpreport rep,
+            bool isrootcall,
+            alglib.smp.shared_pool sessions, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        This function trains neural network ensemble passed to this function using
+        current dataset and early stopping training algorithm. Each early stopping
+        round performs NRestarts  random  restarts  (thus,  EnsembleSize*NRestarts
+        training rounds is performed in total).
+
+
+          -- ALGLIB --
+             Copyright 22.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        private static void mlptrainensemblex(mlptrainer s,
+            mlpe.mlpensemble ensemble,
+            int idx0,
+            int idx1,
+            int nrestarts,
+            int trainingmethod,
+            apserv.sinteger ngrad,
+            bool isrootcall,
+            alglib.smp.shared_pool esessions,
+            alglib.xparams _params)
+        {
+            int pcount = 0;
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int trnsubsetsize = 0;
+            int valsubsetsize = 0;
+            int k0 = 0;
+            apserv.sinteger ngrad0 = new apserv.sinteger();
+            apserv.sinteger ngrad1 = new apserv.sinteger();
+            mlpetrnsession psession = null;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+            int i_ = 0;
+            int i1_ = 0;
+
+            nin = mlpbase.mlpgetinputscount(ensemble.network, _params);
+            nout = mlpbase.mlpgetoutputscount(ensemble.network, _params);
+            wcount = mlpbase.mlpgetweightscount(ensemble.network, _params);
+            if( mlpbase.mlpissoftmax(ensemble.network, _params) )
+            {
+                pcount = nin;
+            }
+            else
+            {
+                pcount = nin+nout;
+            }
+            if( nrestarts<=0 )
+            {
+                nrestarts = 1;
+            }
+            
+            //
+            // Handle degenerate case
+            //
+            if( s.npoints<2 )
+            {
+                for(i=idx0; i<=idx1-1; i++)
+                {
+                    for(j=0; j<=wcount-1; j++)
+                    {
+                        ensemble.weights[i*wcount+j] = 0.0;
+                    }
+                    for(j=0; j<=pcount-1; j++)
+                    {
+                        ensemble.columnmeans[i*pcount+j] = 0.0;
+                        ensemble.columnsigmas[i*pcount+j] = 1.0;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Process root call
+            //
+            if( isrootcall )
+            {
+                
+                //
+                // Try parallelization
+                // We expect that minimum number of iterations before convergence is 100.
+                // Hence is our approach to evaluation of task complexity.
+                //
+                if( (double)(Math.Max(nrestarts, 1)*(idx1-idx0)*apserv.rmul3(2*wcount, s.npoints, 100, _params))>=(double)(apserv.smpactivationlevel(_params)) )
+                {
+                    if( _trypexec_mlptrainensemblex(s,ensemble,idx0,idx1,nrestarts,trainingmethod,ngrad,isrootcall,esessions, _params) )
+                    {
+                        return;
+                    }
+                }
+                
+                //
+                // Prepare:
+                // * prepare MLPETrnSessions
+                // * fill ensemble by zeros (helps to detect errors)
+                //
+                initmlpetrnsessions(ensemble.network, s, esessions, _params);
+                for(i=idx0; i<=idx1-1; i++)
+                {
+                    for(j=0; j<=wcount-1; j++)
+                    {
+                        ensemble.weights[i*wcount+j] = 0.0;
+                    }
+                    for(j=0; j<=pcount-1; j++)
+                    {
+                        ensemble.columnmeans[i*pcount+j] = 0.0;
+                        ensemble.columnsigmas[i*pcount+j] = 0.0;
+                    }
+                }
+                
+                //
+                // Train in non-root mode and exit
+                //
+                mlptrainensemblex(s, ensemble, idx0, idx1, nrestarts, trainingmethod, ngrad, false, esessions, _params);
+                return;
+            }
+            
+            //
+            // Split problem
+            //
+            if( idx1-idx0>=2 )
+            {
+                k0 = (idx1-idx0)/2;
+                ngrad0.val = 0;
+                ngrad1.val = 0;
+                mlptrainensemblex(s, ensemble, idx0, idx0+k0, nrestarts, trainingmethod, ngrad0, false, esessions, _params);
+                mlptrainensemblex(s, ensemble, idx0+k0, idx1, nrestarts, trainingmethod, ngrad1, false, esessions, _params);
+                ngrad.val = ngrad0.val+ngrad1.val;
+                return;
+            }
+            
+            //
+            // Retrieve and prepare session
+            //
+            alglib.smp.ae_shared_pool_retrieve(esessions, ref psession);
+            
+            //
+            // Train
+            //
+            hqrnd.hqrndrandomize(rs, _params);
+            for(k=idx0; k<=idx1-1; k++)
+            {
+                
+                //
+                // Split set
+                //
+                trnsubsetsize = 0;
+                valsubsetsize = 0;
+                if( trainingmethod==0 )
+                {
+                    do
+                    {
+                        trnsubsetsize = 0;
+                        valsubsetsize = 0;
+                        for(i=0; i<=s.npoints-1; i++)
+                        {
+                            if( (double)(math.randomreal())<(double)(0.66) )
+                            {
+                                
+                                //
+                                // Assign sample to training set
+                                //
+                                psession.trnsubset[trnsubsetsize] = i;
+                                trnsubsetsize = trnsubsetsize+1;
+                            }
+                            else
+                            {
+                                
+                                //
+                                // Assign sample to validation set
+                                //
+                                psession.valsubset[valsubsetsize] = i;
+                                valsubsetsize = valsubsetsize+1;
+                            }
+                        }
+                    }
+                    while( !(trnsubsetsize!=0 && valsubsetsize!=0) );
+                }
+                if( trainingmethod==1 )
+                {
+                    valsubsetsize = 0;
+                    trnsubsetsize = s.npoints;
+                    for(i=0; i<=s.npoints-1; i++)
+                    {
+                        psession.trnsubset[i] = hqrnd.hqrnduniformi(rs, s.npoints, _params);
+                    }
+                }
+                
+                //
+                // Train
+                //
+                mlptrainnetworkx(s, nrestarts, -1, psession.trnsubset, trnsubsetsize, psession.valsubset, valsubsetsize, psession.network, psession.mlprep, true, psession.mlpsessions, _params);
+                ngrad.val = ngrad.val+psession.mlprep.ngrad;
+                
+                //
+                // Save results
+                //
+                i1_ = (0) - (k*wcount);
+                for(i_=k*wcount; i_<=(k+1)*wcount-1;i_++)
+                {
+                    ensemble.weights[i_] = psession.network.weights[i_+i1_];
+                }
+                i1_ = (0) - (k*pcount);
+                for(i_=k*pcount; i_<=(k+1)*pcount-1;i_++)
+                {
+                    ensemble.columnmeans[i_] = psession.network.columnmeans[i_+i1_];
+                }
+                i1_ = (0) - (k*pcount);
+                for(i_=k*pcount; i_<=(k+1)*pcount-1;i_++)
+                {
+                    ensemble.columnsigmas[i_] = psession.network.columnsigmas[i_+i1_];
+                }
+            }
+            
+            //
+            // Recycle session
+            //
+            alglib.smp.ae_shared_pool_recycle(esessions, ref psession);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_mlptrainensemblex(mlptrainer s,
+            mlpe.mlpensemble ensemble,
+            int idx0,
+            int idx1,
+            int nrestarts,
+            int trainingmethod,
+            apserv.sinteger ngrad,
+            bool isrootcall,
+            alglib.smp.shared_pool esessions, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        This function performs step-by-step training of the neural  network.  Here
+        "step-by-step" means that training  starts  with  MLPStartTrainingX  call,
+        and then user subsequently calls MLPContinueTrainingX  to perform one more
+        iteration of the training.
+
+        After call to this function trainer object remembers network and  is ready
+        to  train  it.  However,  no  training  is  performed  until first call to 
+        MLPContinueTraining() function. Subsequent calls  to MLPContinueTraining()
+        will advance traing progress one iteration further.
+
+
+          -- ALGLIB --
+             Copyright 13.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        private static void mlpstarttrainingx(mlptrainer s,
+            bool randomstart,
+            int algokind,
+            int[] subset,
+            int subsetsize,
+            smlptrnsession session,
+            alglib.xparams _params)
+        {
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int ntype = 0;
+            int ttype = 0;
+            int i = 0;
+
+            
+            //
+            // Check parameters
+            //
+            alglib.ap.assert(s.npoints>=0, "MLPStartTrainingX: internal error - parameter S is not initialized or is spoiled(S.NPoints<0)");
+            alglib.ap.assert(algokind==0 || algokind==-1, "MLPStartTrainingX: unexpected AlgoKind");
+            if( s.rcpar )
+            {
+                ttype = 0;
+            }
+            else
+            {
+                ttype = 1;
+            }
+            if( !mlpbase.mlpissoftmax(session.network, _params) )
+            {
+                ntype = 0;
+            }
+            else
+            {
+                ntype = 1;
+            }
+            alglib.ap.assert(ntype==ttype, "MLPStartTrainingX: internal error - type of the resulting network is not similar to network type in trainer object");
+            mlpbase.mlpproperties(session.network, ref nin, ref nout, ref wcount, _params);
+            alglib.ap.assert(s.nin==nin, "MLPStartTrainingX: number of inputs in trainer is not equal to number of inputs in the network.");
+            alglib.ap.assert(s.nout==nout, "MLPStartTrainingX: number of outputs in trainer is not equal to number of outputs in the network.");
+            alglib.ap.assert(alglib.ap.len(subset)>=subsetsize, "MLPStartTrainingX: internal error - parameter SubsetSize more than input subset size(Length(Subset)<SubsetSize)");
+            for(i=0; i<=subsetsize-1; i++)
+            {
+                alglib.ap.assert(subset[i]>=0 && subset[i]<=s.npoints-1, "MLPStartTrainingX: internal error - parameter Subset contains incorrect index(Subset[I]<0 or Subset[I]>S.NPoints-1)");
+            }
+            
+            //
+            // Prepare session
+            //
+            minlbfgs.minlbfgssetcond(session.optimizer, 0.0, 0.0, s.wstep, s.maxits, _params);
+            if( s.npoints>0 && subsetsize!=0 )
+            {
+                if( randomstart )
+                {
+                    mlpbase.mlprandomize(session.network, _params);
+                }
+                minlbfgs.minlbfgsrestartfrom(session.optimizer, session.network.weights, _params);
+            }
+            else
+            {
+                for(i=0; i<=wcount-1; i++)
+                {
+                    session.network.weights[i] = 0;
+                }
+            }
+            if( algokind==-1 )
+            {
+                session.algoused = s.algokind;
+                if( s.algokind==1 )
+                {
+                    session.minibatchsize = s.minibatchsize;
+                }
+            }
+            else
+            {
+                session.algoused = 0;
+            }
+            hqrnd.hqrndrandomize(session.generator, _params);
+            session.rstate.ia = new int[15+1];
+            session.rstate.ra = new double[1+1];
+            session.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        This function performs step-by-step training of the neural  network.  Here
+        "step-by-step" means  that training starts  with  MLPStartTrainingX  call,
+        and then user subsequently calls MLPContinueTrainingX  to perform one more
+        iteration of the training.
+
+        This  function  performs  one  more  iteration of the training and returns
+        either True (training continues) or False (training stopped). In case True
+        was returned, Network weights are updated according to the  current  state
+        of the optimization progress. In case False was  returned,  no  additional
+        updates is performed (previous update of  the  network weights moved us to
+        the final point, and no additional updates is needed).
+
+        EXAMPLE:
+            >
+            > [initialize network and trainer object]
+            >
+            > MLPStartTraining(Trainer, Network, True)
+            > while MLPContinueTraining(Trainer, Network) do
+            >     [visualize training progress]
+            >
+
+
+          -- ALGLIB --
+             Copyright 13.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool mlpcontinuetrainingx(mlptrainer s,
+            int[] subset,
+            int subsetsize,
+            ref int ngradbatch,
+            smlptrnsession session,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int twcount = 0;
+            int ntype = 0;
+            int ttype = 0;
+            double decay = 0;
+            double v = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int trnsetsize = 0;
+            int epoch = 0;
+            int minibatchcount = 0;
+            int minibatchidx = 0;
+            int cursize = 0;
+            int idx0 = 0;
+            int idx1 = 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( session.rstate.stage>=0 )
+            {
+                nin = session.rstate.ia[0];
+                nout = session.rstate.ia[1];
+                wcount = session.rstate.ia[2];
+                twcount = session.rstate.ia[3];
+                ntype = session.rstate.ia[4];
+                ttype = session.rstate.ia[5];
+                i = session.rstate.ia[6];
+                j = session.rstate.ia[7];
+                k = session.rstate.ia[8];
+                trnsetsize = session.rstate.ia[9];
+                epoch = session.rstate.ia[10];
+                minibatchcount = session.rstate.ia[11];
+                minibatchidx = session.rstate.ia[12];
+                cursize = session.rstate.ia[13];
+                idx0 = session.rstate.ia[14];
+                idx1 = session.rstate.ia[15];
+                decay = session.rstate.ra[0];
+                v = session.rstate.ra[1];
+            }
+            else
+            {
+                nin = 359;
+                nout = -58;
+                wcount = -919;
+                twcount = -909;
+                ntype = 81;
+                ttype = 255;
+                i = 74;
+                j = -788;
+                k = 809;
+                trnsetsize = 205;
+                epoch = -838;
+                minibatchcount = 939;
+                minibatchidx = -526;
+                cursize = 763;
+                idx0 = -541;
+                idx1 = -698;
+                decay = -900;
+                v = -318;
+            }
+            if( session.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // Check correctness of inputs
+            //
+            alglib.ap.assert(s.npoints>=0, "MLPContinueTrainingX: internal error - parameter S is not initialized or is spoiled(S.NPoints<0).");
+            if( s.rcpar )
+            {
+                ttype = 0;
+            }
+            else
+            {
+                ttype = 1;
+            }
+            if( !mlpbase.mlpissoftmax(session.network, _params) )
+            {
+                ntype = 0;
+            }
+            else
+            {
+                ntype = 1;
+            }
+            alglib.ap.assert(ntype==ttype, "MLPContinueTrainingX: internal error - type of the resulting network is not similar to network type in trainer object.");
+            mlpbase.mlpproperties(session.network, ref nin, ref nout, ref wcount, _params);
+            alglib.ap.assert(s.nin==nin, "MLPContinueTrainingX: internal error - number of inputs in trainer is not equal to number of inputs in the network.");
+            alglib.ap.assert(s.nout==nout, "MLPContinueTrainingX: internal error - number of outputs in trainer is not equal to number of outputs in the network.");
+            alglib.ap.assert(alglib.ap.len(subset)>=subsetsize, "MLPContinueTrainingX: internal error - parameter SubsetSize more than input subset size(Length(Subset)<SubsetSize).");
+            for(i=0; i<=subsetsize-1; i++)
+            {
+                alglib.ap.assert(subset[i]>=0 && subset[i]<=s.npoints-1, "MLPContinueTrainingX: internal error - parameter Subset contains incorrect index(Subset[I]<0 or Subset[I]>S.NPoints-1).");
+            }
+            
+            //
+            // Quick exit on empty training set
+            //
+            if( s.npoints==0 || subsetsize==0 )
+            {
+                result = false;
+                return result;
+            }
+            
+            //
+            // Minibatch training
+            //
+            if( session.algoused==1 )
+            {
+                alglib.ap.assert(false, "MINIBATCH TRAINING IS NOT IMPLEMENTED YET");
+            }
+            
+            //
+            // Last option: full batch training
+            //
+            decay = s.decay;
+        lbl_1:
+            if( !minlbfgs.minlbfgsiteration(session.optimizer, _params) )
+            {
+                goto lbl_2;
+            }
+            if( !session.optimizer.xupdated )
+            {
+                goto lbl_3;
+            }
+            for(i_=0; i_<=wcount-1;i_++)
+            {
+                session.network.weights[i_] = session.optimizer.x[i_];
+            }
+            session.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+        lbl_3:
+            for(i_=0; i_<=wcount-1;i_++)
+            {
+                session.network.weights[i_] = session.optimizer.x[i_];
+            }
+            if( s.datatype==0 )
+            {
+                mlpbase.mlpgradbatchsubset(session.network, s.densexy, s.npoints, subset, subsetsize, ref session.optimizer.f, ref session.optimizer.g, _params);
+            }
+            if( s.datatype==1 )
+            {
+                mlpbase.mlpgradbatchsparsesubset(session.network, s.sparsexy, s.npoints, subset, subsetsize, ref session.optimizer.f, ref session.optimizer.g, _params);
+            }
+            
+            //
+            // Increment number of operations performed on batch gradient
+            //
+            ngradbatch = ngradbatch+1;
+            v = 0.0;
+            for(i_=0; i_<=wcount-1;i_++)
+            {
+                v += session.network.weights[i_]*session.network.weights[i_];
+            }
+            session.optimizer.f = session.optimizer.f+0.5*decay*v;
+            for(i_=0; i_<=wcount-1;i_++)
+            {
+                session.optimizer.g[i_] = session.optimizer.g[i_] + decay*session.network.weights[i_];
+            }
+            goto lbl_1;
+        lbl_2:
+            minlbfgs.minlbfgsresultsbuf(session.optimizer, ref session.network.weights, session.optimizerrep, _params);
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            session.rstate.ia[0] = nin;
+            session.rstate.ia[1] = nout;
+            session.rstate.ia[2] = wcount;
+            session.rstate.ia[3] = twcount;
+            session.rstate.ia[4] = ntype;
+            session.rstate.ia[5] = ttype;
+            session.rstate.ia[6] = i;
+            session.rstate.ia[7] = j;
+            session.rstate.ia[8] = k;
+            session.rstate.ia[9] = trnsetsize;
+            session.rstate.ia[10] = epoch;
+            session.rstate.ia[11] = minibatchcount;
+            session.rstate.ia[12] = minibatchidx;
+            session.rstate.ia[13] = cursize;
+            session.rstate.ia[14] = idx0;
+            session.rstate.ia[15] = idx1;
+            session.rstate.ra[0] = decay;
+            session.rstate.ra[1] = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal bagging subroutine.
+
+          -- ALGLIB --
+             Copyright 19.02.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void mlpebagginginternal(mlpe.mlpensemble ensemble,
+            double[,] xy,
+            int npoints,
+            double decay,
+            int restarts,
+            double wstep,
+            int maxits,
+            bool lmalgorithm,
+            ref int info,
+            mlpreport rep,
+            mlpcvreport ooberrors,
+            alglib.xparams _params)
+        {
+            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;
+            mlpreport tmprep = new mlpreport();
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+            int i_ = 0;
+            int i1_ = 0;
+
+            info = 0;
+
+            nin = mlpbase.mlpgetinputscount(ensemble.network, _params);
+            nout = mlpbase.mlpgetoutputscount(ensemble.network, _params);
+            wcount = mlpbase.mlpgetweightscount(ensemble.network, _params);
+            
+            //
+            // 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, _params) )
+            {
+                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, _params) )
+            {
+                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, _params) )
+            {
+                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
+            //
+            hqrnd.hqrndrandomize(rs, _params);
+            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 = hqrnd.hqrnduniformi(rs, npoints, _params);
+                    s[j] = true;
+                    for(i_=0; i_<=ccnt-1;i_++)
+                    {
+                        xys[i,i_] = xy[j,i_];
+                    }
+                }
+                
+                //
+                // train
+                //
+                if( lmalgorithm )
+                {
+                    mlptrainlm(ensemble.network, xys, npoints, decay, restarts, ref info, tmprep, _params);
+                }
+                else
+                {
+                    mlptrainlbfgs(ensemble.network, xys, npoints, decay, restarts, wstep, maxits, ref info, tmprep, _params);
+                }
+                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, _params);
+                        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, _params) )
+            {
+                bdss.dserrallocate(nout, ref dsbuf, _params);
+            }
+            else
+            {
+                bdss.dserrallocate(-nout, ref dsbuf, _params);
+            }
+            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, _params) )
+                    {
+                        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, _params);
+                }
+            }
+            bdss.dserrfinish(ref dsbuf, _params);
+            ooberrors.relclserror = dsbuf[0];
+            ooberrors.avgce = dsbuf[1];
+            ooberrors.rmserror = dsbuf[2];
+            ooberrors.avgerror = dsbuf[3];
+            ooberrors.avgrelerror = dsbuf[4];
+        }
+
+
+        /*************************************************************************
+        This function initializes temporaries needed for training session.
+
+
+          -- ALGLIB --
+             Copyright 01.07.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void initmlptrnsession(mlpbase.multilayerperceptron networktrained,
+            bool randomizenetwork,
+            mlptrainer trainer,
+            smlptrnsession session,
+            alglib.xparams _params)
+        {
+            int nin = 0;
+            int nout = 0;
+            int wcount = 0;
+            int pcount = 0;
+            int[] dummysubset = new int[0];
+
+            
+            //
+            // Prepare network:
+            // * copy input network to Session.Network
+            // * re-initialize preprocessor and weights if RandomizeNetwork=True
+            //
+            mlpbase.mlpcopy(networktrained, session.network, _params);
+            if( randomizenetwork )
+            {
+                alglib.ap.assert(trainer.datatype==0 || trainer.datatype==1, "InitTemporaries: unexpected Trainer.DataType");
+                if( trainer.datatype==0 )
+                {
+                    mlpbase.mlpinitpreprocessorsubset(session.network, trainer.densexy, trainer.npoints, dummysubset, -1, _params);
+                }
+                if( trainer.datatype==1 )
+                {
+                    mlpbase.mlpinitpreprocessorsparsesubset(session.network, trainer.sparsexy, trainer.npoints, dummysubset, -1, _params);
+                }
+                mlpbase.mlprandomize(session.network, _params);
+                session.randomizenetwork = true;
+            }
+            else
+            {
+                session.randomizenetwork = false;
+            }
+            
+            //
+            // Determine network geometry and initialize optimizer 
+            //
+            mlpbase.mlpproperties(session.network, ref nin, ref nout, ref wcount, _params);
+            minlbfgs.minlbfgscreate(wcount, Math.Min(wcount, trainer.lbfgsfactor), session.network.weights, session.optimizer, _params);
+            minlbfgs.minlbfgssetxrep(session.optimizer, true, _params);
+            
+            //
+            // Create buffers
+            //
+            session.wbuf0 = new double[wcount];
+            session.wbuf1 = new double[wcount];
+            
+            //
+            // Initialize session result
+            //
+            mlpbase.mlpexporttunableparameters(session.network, ref session.bestparameters, ref pcount, _params);
+            session.bestrmserror = math.maxrealnumber;
+        }
+
+
+        /*************************************************************************
+        This function initializes temporaries needed for training session.
+
+        *************************************************************************/
+        private static void initmlptrnsessions(mlpbase.multilayerperceptron networktrained,
+            bool randomizenetwork,
+            mlptrainer trainer,
+            alglib.smp.shared_pool sessions,
+            alglib.xparams _params)
+        {
+            int[] dummysubset = new int[0];
+            smlptrnsession t = new smlptrnsession();
+            smlptrnsession p = null;
+
+            if( alglib.smp.ae_shared_pool_is_initialized(sessions) )
+            {
+                
+                //
+                // Pool was already initialized.
+                // Clear sessions stored in the pool.
+                //
+                alglib.smp.ae_shared_pool_first_recycled(sessions, ref p);
+                while( p!=null )
+                {
+                    alglib.ap.assert(mlpbase.mlpsamearchitecture(p.network, networktrained, _params), "InitMLPTrnSessions: internal consistency error");
+                    p.bestrmserror = math.maxrealnumber;
+                    alglib.smp.ae_shared_pool_next_recycled(sessions, ref p);
+                }
+            }
+            else
+            {
+                
+                //
+                // Prepare session and seed pool
+                //
+                initmlptrnsession(networktrained, randomizenetwork, trainer, t, _params);
+                alglib.smp.ae_shared_pool_set_seed(sessions, t);
+            }
+        }
+
+
+        /*************************************************************************
+        This function initializes temporaries needed for ensemble training.
+
+        *************************************************************************/
+        private static void initmlpetrnsession(mlpbase.multilayerperceptron individualnetwork,
+            mlptrainer trainer,
+            mlpetrnsession session,
+            alglib.xparams _params)
+        {
+            int[] dummysubset = new int[0];
+
+            
+            //
+            // Prepare network:
+            // * copy input network to Session.Network
+            // * re-initialize preprocessor and weights if RandomizeNetwork=True
+            //
+            mlpbase.mlpcopy(individualnetwork, session.network, _params);
+            initmlptrnsessions(individualnetwork, true, trainer, session.mlpsessions, _params);
+            apserv.ivectorsetlengthatleast(ref session.trnsubset, trainer.npoints, _params);
+            apserv.ivectorsetlengthatleast(ref session.valsubset, trainer.npoints, _params);
+        }
+
+
+        /*************************************************************************
+        This function initializes temporaries needed for training session.
+
+        *************************************************************************/
+        private static void initmlpetrnsessions(mlpbase.multilayerperceptron individualnetwork,
+            mlptrainer trainer,
+            alglib.smp.shared_pool sessions,
+            alglib.xparams _params)
+        {
+            mlpetrnsession t = new mlpetrnsession();
+
+            if( !alglib.smp.ae_shared_pool_is_initialized(sessions) )
+            {
+                initmlpetrnsession(individualnetwork, trainer, t, _params);
+                alglib.smp.ae_shared_pool_set_seed(sessions, t);
+            }
+        }
+
+
+    }
+    public class clustering
+    {
+        /*************************************************************************
+        This structure is used to  store  temporaries  for  KMeansGenerateInternal
+        function, so we will be able to  reuse  them  during  multiple  subsequent
+        calls.
+            
+          -- ALGLIB --
+             Copyright 24.07.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public class kmeansbuffers : apobject
+        {
+            public double[,] ct;
+            public double[,] ctbest;
+            public int[] xycbest;
+            public int[] xycprev;
+            public double[] d2;
+            public int[] csizes;
+            public apserv.apbuffers initbuf;
+            public alglib.smp.shared_pool updatepool;
+            public kmeansbuffers()
+            {
+                init();
+            }
+            public override void init()
+            {
+                ct = new double[0,0];
+                ctbest = new double[0,0];
+                xycbest = new int[0];
+                xycprev = new int[0];
+                d2 = new double[0];
+                csizes = new int[0];
+                initbuf = new apserv.apbuffers();
+                updatepool = new alglib.smp.shared_pool();
+            }
+            public override alglib.apobject make_copy()
+            {
+                kmeansbuffers _result = new kmeansbuffers();
+                _result.ct = (double[,])ct.Clone();
+                _result.ctbest = (double[,])ctbest.Clone();
+                _result.xycbest = (int[])xycbest.Clone();
+                _result.xycprev = (int[])xycprev.Clone();
+                _result.d2 = (double[])d2.Clone();
+                _result.csizes = (int[])csizes.Clone();
+                _result.initbuf = (apserv.apbuffers)initbuf.make_copy();
+                _result.updatepool = (alglib.smp.shared_pool)updatepool.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure is a clusterization engine.
+
+        You should not try to access its fields directly.
+        Use ALGLIB functions in order to work with this object.
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public class clusterizerstate : apobject
+        {
+            public int npoints;
+            public int nfeatures;
+            public int disttype;
+            public double[,] xy;
+            public double[,] d;
+            public int ahcalgo;
+            public int kmeansrestarts;
+            public int kmeansmaxits;
+            public int kmeansinitalgo;
+            public bool kmeansdbgnoits;
+            public int seed;
+            public double[,] tmpd;
+            public apserv.apbuffers distbuf;
+            public kmeansbuffers kmeanstmp;
+            public clusterizerstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                xy = new double[0,0];
+                d = new double[0,0];
+                tmpd = new double[0,0];
+                distbuf = new apserv.apbuffers();
+                kmeanstmp = new kmeansbuffers();
+            }
+            public override alglib.apobject make_copy()
+            {
+                clusterizerstate _result = new clusterizerstate();
+                _result.npoints = npoints;
+                _result.nfeatures = nfeatures;
+                _result.disttype = disttype;
+                _result.xy = (double[,])xy.Clone();
+                _result.d = (double[,])d.Clone();
+                _result.ahcalgo = ahcalgo;
+                _result.kmeansrestarts = kmeansrestarts;
+                _result.kmeansmaxits = kmeansmaxits;
+                _result.kmeansinitalgo = kmeansinitalgo;
+                _result.kmeansdbgnoits = kmeansdbgnoits;
+                _result.seed = seed;
+                _result.tmpd = (double[,])tmpd.Clone();
+                _result.distbuf = (apserv.apbuffers)distbuf.make_copy();
+                _result.kmeanstmp = (kmeansbuffers)kmeanstmp.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure  is used to store results of the agglomerative hierarchical
+        clustering (AHC).
+
+        Following information is returned:
+
+        * TerminationType - completion code:
+          * 1   for successful completion of algorithm
+          * -5  inappropriate combination of  clustering  algorithm  and  distance
+                function was used. As for now, it  is  possible  only when  Ward's
+                method is called for dataset with non-Euclidean distance function.
+          In case negative completion code is returned,  other  fields  of  report
+          structure are invalid and should not be used.
+
+        * NPoints contains number of points in the original dataset
+
+        * Z contains information about merges performed  (see below).  Z  contains
+          indexes from the original (unsorted) dataset and it can be used when you
+          need to know what points were merged. However, it is not convenient when
+          you want to build a dendrograd (see below).
+          
+        * if  you  want  to  build  dendrogram, you  can use Z, but it is not good
+          option, because Z contains  indexes from  unsorted  dataset.  Dendrogram
+          built from such dataset is likely to have intersections. So, you have to
+          reorder you points before building dendrogram.
+          Permutation which reorders point is returned in P. Another representation
+          of  merges,  which  is  more  convenient for dendorgram construction, is
+          returned in PM.
+          
+        * more information on format of Z, P and PM can be found below and in the
+          examples from ALGLIB Reference Manual.
+
+        FORMAL DESCRIPTION OF FIELDS:
+            NPoints         number of points
+            Z               array[NPoints-1,2],  contains   indexes   of  clusters
+                            linked in pairs to  form  clustering  tree.  I-th  row
+                            corresponds to I-th merge:
+                            * Z[I,0] - index of the first cluster to merge
+                            * Z[I,1] - index of the second cluster to merge
+                            * Z[I,0]<Z[I,1]
+                            * clusters are  numbered  from 0 to 2*NPoints-2,  with
+                              indexes from 0 to NPoints-1 corresponding to  points
+                              of the original dataset, and indexes from NPoints to
+                              2*NPoints-2  correspond  to  clusters  generated  by
+                              subsequent  merges  (I-th  row  of Z creates cluster
+                              with index NPoints+I).
+                            
+                            IMPORTANT: indexes in Z[] are indexes in the ORIGINAL,
+                            unsorted dataset. In addition to  Z algorithm  outputs
+                            permutation which rearranges points in such  way  that
+                            subsequent merges are  performed  on  adjacent  points
+                            (such order is needed if you want to build dendrogram).
+                            However,  indexes  in  Z  are  related  to   original,
+                            unrearranged sequence of points.
+                            
+            P               array[NPoints], permutation which reorders points  for
+                            dendrogram  construction.  P[i] contains  index of the
+                            position  where  we  should  move  I-th  point  of the
+                            original dataset in order to apply merges PZ/PM.
+
+            PZ              same as Z, but for permutation of points given  by  P.
+                            The  only  thing  which  changed  are  indexes  of the
+                            original points; indexes of clusters remained same.
+                            
+            MergeDist       array[NPoints-1], contains distances between  clusters
+                            being merged (MergeDist[i] correspond to merge  stored
+                            in Z[i,...]):
+                            * CLINK, SLINK and  average  linkage algorithms report
+                              "raw", unmodified distance metric.
+                            * Ward's   method   reports   weighted   intra-cluster
+                              variance, which is equal to ||Ca-Cb||^2 * Sa*Sb/(Sa+Sb).
+                              Here  A  and  B  are  clusters being merged, Ca is a
+                              center of A, Cb is a center of B, Sa is a size of A,
+                              Sb is a size of B.
+                            
+            PM              array[NPoints-1,6], another representation of  merges,
+                            which is suited for dendrogram construction. It  deals
+                            with rearranged points (permutation P is applied)  and
+                            represents merges in a form which different  from  one
+                            used by Z.
+                            For each I from 0 to NPoints-2, I-th row of PM represents
+                            merge performed on two clusters C0 and C1. Here:
+                            * C0 contains points with indexes PM[I,0]...PM[I,1]
+                            * C1 contains points with indexes PM[I,2]...PM[I,3]
+                            * indexes stored in PM are given for dataset sorted
+                              according to permutation P
+                            * PM[I,1]=PM[I,2]-1 (only adjacent clusters are merged)
+                            * PM[I,0]<=PM[I,1], PM[I,2]<=PM[I,3], i.e. both
+                              clusters contain at least one point
+                            * heights of "subdendrograms" corresponding  to  C0/C1
+                              are stored in PM[I,4]  and  PM[I,5].  Subdendrograms
+                              corresponding   to   single-point   clusters    have
+                              height=0. Dendrogram of the merge result has  height
+                              H=max(H0,H1)+1.
+
+        NOTE: there is one-to-one correspondence between merges described by Z and
+              PM. I-th row of Z describes same merge of clusters as I-th row of PM,
+              with "left" cluster from Z corresponding to the "left" one from PM.
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public class ahcreport : apobject
+        {
+            public int terminationtype;
+            public int npoints;
+            public int[] p;
+            public int[,] z;
+            public int[,] pz;
+            public int[,] pm;
+            public double[] mergedist;
+            public ahcreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+                p = new int[0];
+                z = new int[0,0];
+                pz = new int[0,0];
+                pm = new int[0,0];
+                mergedist = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                ahcreport _result = new ahcreport();
+                _result.terminationtype = terminationtype;
+                _result.npoints = npoints;
+                _result.p = (int[])p.Clone();
+                _result.z = (int[,])z.Clone();
+                _result.pz = (int[,])pz.Clone();
+                _result.pm = (int[,])pm.Clone();
+                _result.mergedist = (double[])mergedist.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This  structure   is  used  to  store  results of the  k-means  clustering
+        algorithm.
+
+        Following information is always returned:
+        * NPoints contains number of points in the original dataset
+        * TerminationType contains completion code, negative on failure, positive
+          on success
+        * K contains number of clusters
+
+        For positive TerminationType we return:
+        * NFeatures contains number of variables in the original dataset
+        * C, which contains centers found by algorithm
+        * CIdx, which maps points of the original dataset to clusters
+
+        FORMAL DESCRIPTION OF FIELDS:
+            NPoints         number of points, >=0
+            NFeatures       number of variables, >=1
+            TerminationType completion code:
+                            * -5 if  distance  type  is  anything  different  from
+                                 Euclidean metric
+                            * -3 for degenerate dataset: a) less  than  K  distinct
+                                 points, b) K=0 for non-empty dataset.
+                            * +1 for successful completion
+            K               number of clusters
+            C               array[K,NFeatures], rows of the array store centers
+            CIdx            array[NPoints], which contains cluster indexes
+            IterationsCount actual number of iterations performed by clusterizer.
+                            If algorithm performed more than one random restart,
+                            total number of iterations is returned.
+            Energy          merit function, "energy", sum  of  squared  deviations
+                            from cluster centers
+            
+          -- ALGLIB --
+             Copyright 27.11.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public class kmeansreport : apobject
+        {
+            public int npoints;
+            public int nfeatures;
+            public int terminationtype;
+            public int iterationscount;
+            public double energy;
+            public int k;
+            public double[,] c;
+            public int[] cidx;
+            public kmeansreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+                c = new double[0,0];
+                cidx = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                kmeansreport _result = new kmeansreport();
+                _result.npoints = npoints;
+                _result.nfeatures = nfeatures;
+                _result.terminationtype = terminationtype;
+                _result.iterationscount = iterationscount;
+                _result.energy = energy;
+                _result.k = k;
+                _result.c = (double[,])c.Clone();
+                _result.cidx = (int[])cidx.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        public const int kmeansblocksize = 32;
+        public const int kmeansparalleldim = 8;
+        public const int kmeansparallelk = 4;
+        public const double complexitymultiplier = 1.0;
+
+
+        /*************************************************************************
+        This function initializes clusterizer object. Newly initialized object  is
+        empty, i.e. it does not contain dataset. You should use it as follows:
+        1. creation
+        2. dataset is added with ClusterizerSetPoints()
+        3. additional parameters are set
+        3. clusterization is performed with one of the clustering functions
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizercreate(clusterizerstate s,
+            alglib.xparams _params)
+        {
+            s.npoints = 0;
+            s.nfeatures = 0;
+            s.disttype = 2;
+            s.ahcalgo = 0;
+            s.kmeansrestarts = 1;
+            s.kmeansmaxits = 0;
+            s.kmeansinitalgo = 0;
+            s.kmeansdbgnoits = false;
+            s.seed = 1;
+            kmeansinitbuf(s.kmeanstmp, _params);
+        }
+
+
+        /*************************************************************************
+        This function adds dataset to the clusterizer structure.
+
+        This function overrides all previous calls  of  ClusterizerSetPoints()  or
+        ClusterizerSetDistances().
+
+        INPUT PARAMETERS:
+            S       -   clusterizer state, initialized by ClusterizerCreate()
+            XY      -   array[NPoints,NFeatures], dataset
+            NPoints -   number of points, >=0
+            NFeatures-  number of features, >=1
+            DistType-   distance function:
+                        *  0    Chebyshev distance  (L-inf norm)
+                        *  1    city block distance (L1 norm)
+                        *  2    Euclidean distance  (L2 norm), non-squared
+                        * 10    Pearson correlation:
+                                dist(a,b) = 1-corr(a,b)
+                        * 11    Absolute Pearson correlation:
+                                dist(a,b) = 1-|corr(a,b)|
+                        * 12    Uncentered Pearson correlation (cosine of the angle):
+                                dist(a,b) = a'*b/(|a|*|b|)
+                        * 13    Absolute uncentered Pearson correlation
+                                dist(a,b) = |a'*b|/(|a|*|b|)
+                        * 20    Spearman rank correlation:
+                                dist(a,b) = 1-rankcorr(a,b)
+                        * 21    Absolute Spearman rank correlation
+                                dist(a,b) = 1-|rankcorr(a,b)|
+
+        NOTE 1: different distance functions have different performance penalty:
+                * Euclidean or Pearson correlation distances are the fastest ones
+                * Spearman correlation distance function is a bit slower
+                * city block and Chebyshev distances are order of magnitude slower
+               
+                The reason behing difference in performance is that correlation-based
+                distance functions are computed using optimized linear algebra kernels,
+                while Chebyshev and city block distance functions are computed using
+                simple nested loops with two branches at each iteration.
+                
+        NOTE 2: different clustering algorithms have different limitations:
+                * agglomerative hierarchical clustering algorithms may be used with
+                  any kind of distance metric
+                * k-means++ clustering algorithm may be used only  with  Euclidean
+                  distance function
+                Thus, list of specific clustering algorithms you may  use  depends
+                on distance function you specify when you set your dataset.
+               
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizersetpoints(clusterizerstate s,
+            double[,] xy,
+            int npoints,
+            int nfeatures,
+            int disttype,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            alglib.ap.assert((((((((disttype==0 || disttype==1) || disttype==2) || disttype==10) || disttype==11) || disttype==12) || disttype==13) || disttype==20) || disttype==21, "ClusterizerSetPoints: incorrect DistType");
+            alglib.ap.assert(npoints>=0, "ClusterizerSetPoints: NPoints<0");
+            alglib.ap.assert(nfeatures>=1, "ClusterizerSetPoints: NFeatures<1");
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "ClusterizerSetPoints: Rows(XY)<NPoints");
+            alglib.ap.assert(alglib.ap.cols(xy)>=nfeatures, "ClusterizerSetPoints: Cols(XY)<NFeatures");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, npoints, nfeatures, _params), "ClusterizerSetPoints: XY contains NAN/INF");
+            s.npoints = npoints;
+            s.nfeatures = nfeatures;
+            s.disttype = disttype;
+            apserv.rmatrixsetlengthatleast(ref s.xy, npoints, nfeatures, _params);
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(i_=0; i_<=nfeatures-1;i_++)
+                {
+                    s.xy[i,i_] = xy[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function adds dataset given by distance  matrix  to  the  clusterizer
+        structure. It is important that dataset is not  given  explicitly  -  only
+        distance matrix is given.
+
+        This function overrides all previous calls  of  ClusterizerSetPoints()  or
+        ClusterizerSetDistances().
+
+        INPUT PARAMETERS:
+            S       -   clusterizer state, initialized by ClusterizerCreate()
+            D       -   array[NPoints,NPoints], distance matrix given by its upper
+                        or lower triangle (main diagonal is  ignored  because  its
+                        entries are expected to be zero).
+            NPoints -   number of points
+            IsUpper -   whether upper or lower triangle of D is given.
+                
+        NOTE 1: different clustering algorithms have different limitations:
+                * agglomerative hierarchical clustering algorithms may be used with
+                  any kind of distance metric, including one  which  is  given  by
+                  distance matrix
+                * k-means++ clustering algorithm may be used only  with  Euclidean
+                  distance function and explicitly given points - it  can  not  be
+                  used with dataset given by distance matrix
+                Thus, if you call this function, you will be unable to use k-means
+                clustering algorithm to process your problem.
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizersetdistances(clusterizerstate s,
+            double[,] d,
+            int npoints,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int j0 = 0;
+            int j1 = 0;
+
+            alglib.ap.assert(npoints>=0, "ClusterizerSetDistances: NPoints<0");
+            alglib.ap.assert(alglib.ap.rows(d)>=npoints, "ClusterizerSetDistances: Rows(D)<NPoints");
+            alglib.ap.assert(alglib.ap.cols(d)>=npoints, "ClusterizerSetDistances: Cols(D)<NPoints");
+            s.npoints = npoints;
+            s.nfeatures = 0;
+            s.disttype = -1;
+            apserv.rmatrixsetlengthatleast(ref s.d, npoints, npoints, _params);
+            for(i=0; i<=npoints-1; i++)
+            {
+                if( isupper )
+                {
+                    j0 = i+1;
+                    j1 = npoints-1;
+                }
+                else
+                {
+                    j0 = 0;
+                    j1 = i-1;
+                }
+                for(j=j0; j<=j1; j++)
+                {
+                    alglib.ap.assert(math.isfinite(d[i,j]) && (double)(d[i,j])>=(double)(0), "ClusterizerSetDistances: D contains infinite, NAN or negative elements");
+                    s.d[i,j] = d[i,j];
+                    s.d[j,i] = d[i,j];
+                }
+                s.d[i,i] = 0;
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets agglomerative hierarchical clustering algorithm
+
+        INPUT PARAMETERS:
+            S       -   clusterizer state, initialized by ClusterizerCreate()
+            Algo    -   algorithm type:
+                        * 0     complete linkage (default algorithm)
+                        * 1     single linkage
+                        * 2     unweighted average linkage
+                        * 3     weighted average linkage
+                        * 4     Ward's method
+
+        NOTE: Ward's method works correctly only with Euclidean  distance,  that's
+              why algorithm will return negative termination  code  (failure)  for
+              any other distance type.
+              
+              It is possible, however,  to  use  this  method  with  user-supplied
+              distance matrix. It  is  your  responsibility  to pass one which was
+              calculated with Euclidean distance function.
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizersetahcalgo(clusterizerstate s,
+            int algo,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert((((algo==0 || algo==1) || algo==2) || algo==3) || algo==4, "ClusterizerSetHCAlgo: incorrect algorithm type");
+            s.ahcalgo = algo;
+        }
+
+
+        /*************************************************************************
+        This  function  sets k-means properties:  number  of  restarts and maximum
+        number of iterations per one run.
+
+        INPUT PARAMETERS:
+            S       -   clusterizer state, initialized by ClusterizerCreate()
+            Restarts-   restarts count, >=1.
+                        k-means++ algorithm performs several restarts and  chooses
+                        best set of centers (one with minimum squared distance).
+            MaxIts  -   maximum number of k-means iterations performed during  one
+                        run. >=0, zero value means that algorithm performs unlimited
+                        number of iterations.
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizersetkmeanslimits(clusterizerstate s,
+            int restarts,
+            int maxits,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(restarts>=1, "ClusterizerSetKMeansLimits: Restarts<=0");
+            alglib.ap.assert(maxits>=0, "ClusterizerSetKMeansLimits: MaxIts<0");
+            s.kmeansrestarts = restarts;
+            s.kmeansmaxits = maxits;
+        }
+
+
+        /*************************************************************************
+        This function sets k-means  initialization  algorithm.  Several  different
+        algorithms can be chosen, including k-means++.
+
+        INPUT PARAMETERS:
+            S       -   clusterizer state, initialized by ClusterizerCreate()
+            InitAlgo-   initialization algorithm:
+                        * 0  automatic selection ( different  versions  of  ALGLIB
+                             may select different algorithms)
+                        * 1  random initialization
+                        * 2  k-means++ initialization  (best  quality  of  initial
+                             centers, but long  non-parallelizable  initialization
+                             phase with bad cache locality)
+                        * 3  "fast-greedy"  algorithm  with  efficient,  easy   to
+                             parallelize initialization. Quality of initial centers
+                             is  somewhat  worse  than  that  of  k-means++.  This
+                             algorithm is a default one in the current version  of
+                             ALGLIB.
+                        *-1  "debug" algorithm which always selects first  K  rows
+                             of dataset; this algorithm is used for debug purposes
+                             only. Do not use it in the industrial code!
+
+          -- ALGLIB --
+             Copyright 21.01.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizersetkmeansinit(clusterizerstate s,
+            int initalgo,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(initalgo>=-1 && initalgo<=3, "ClusterizerSetKMeansInit: InitAlgo is incorrect");
+            s.kmeansinitalgo = initalgo;
+        }
+
+
+        /*************************************************************************
+        This  function  sets  seed  which  is  used to initialize internal RNG. By
+        default, deterministic seed is used - same for each run of clusterizer. If
+        you specify non-deterministic  seed  value,  then  some  algorithms  which
+        depend on random initialization (in current version: k-means)  may  return
+        slightly different results after each run.
+
+        INPUT PARAMETERS:
+            S       -   clusterizer state, initialized by ClusterizerCreate()
+            Seed    -   seed:
+                        * positive values = use deterministic seed for each run of
+                          algorithms which depend on random initialization
+                        * zero or negative values = use non-deterministic seed
+
+          -- ALGLIB --
+             Copyright 08.06.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizersetseed(clusterizerstate s,
+            int seed,
+            alglib.xparams _params)
+        {
+            s.seed = seed;
+        }
+
+
+        /*************************************************************************
+        This function performs agglomerative hierarchical clustering
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        NOTE: Agglomerative  hierarchical  clustering  algorithm  has two  phases:
+              distance matrix calculation and clustering  itself. Only first phase
+              (distance matrix  calculation)  is  accelerated  by  Intel  MKL  and 
+              multithreading. Thus, acceleration is significant only for medium or
+              high-dimensional problems.
+              
+              Although activating multithreading gives some speedup  over  single-
+              threaded execution, you  should  not  expect  nearly-linear  scaling
+              with respect to cores count.
+
+        INPUT PARAMETERS:
+            S       -   clusterizer state, initialized by ClusterizerCreate()
+
+        OUTPUT PARAMETERS:
+            Rep     -   clustering results; see description of AHCReport
+                        structure for more information.
+
+        NOTE 1: hierarchical clustering algorithms require large amounts of memory.
+                In particular, this implementation needs  sizeof(double)*NPoints^2
+                bytes, which are used to store distance matrix. In  case  we  work
+                with user-supplied matrix, this amount is multiplied by 2 (we have
+                to store original matrix and to work with its copy).
+                
+                For example, problem with 10000 points  would require 800M of RAM,
+                even when working in a 1-dimensional space.
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizerrunahc(clusterizerstate s,
+            ahcreport rep,
+            alglib.xparams _params)
+        {
+            int npoints = 0;
+            int nfeatures = 0;
+
+            npoints = s.npoints;
+            nfeatures = s.nfeatures;
+            
+            //
+            // Fill Rep.NPoints, quick exit when NPoints<=1
+            //
+            rep.npoints = npoints;
+            if( npoints==0 )
+            {
+                rep.p = new int[0];
+                rep.z = new int[0, 0];
+                rep.pz = new int[0, 0];
+                rep.pm = new int[0, 0];
+                rep.mergedist = new double[0];
+                rep.terminationtype = 1;
+                return;
+            }
+            if( npoints==1 )
+            {
+                rep.p = new int[1];
+                rep.z = new int[0, 0];
+                rep.pz = new int[0, 0];
+                rep.pm = new int[0, 0];
+                rep.mergedist = new double[0];
+                rep.p[0] = 0;
+                rep.terminationtype = 1;
+                return;
+            }
+            
+            //
+            // More than one point
+            //
+            if( s.disttype==-1 )
+            {
+                
+                //
+                // Run clusterizer with user-supplied distance matrix
+                //
+                clusterizerrunahcinternal(s, ref s.d, rep, _params);
+                return;
+            }
+            else
+            {
+                
+                //
+                // Check combination of AHC algo and distance type
+                //
+                if( s.ahcalgo==4 && s.disttype!=2 )
+                {
+                    rep.terminationtype = -5;
+                    return;
+                }
+                
+                //
+                // Build distance matrix D.
+                //
+                clusterizergetdistancesbuf(s.distbuf, s.xy, npoints, nfeatures, s.disttype, ref s.tmpd, _params);
+                
+                //
+                // Run clusterizer
+                //
+                clusterizerrunahcinternal(s, ref s.tmpd, rep, _params);
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function performs clustering by k-means++ algorithm.
+
+        You may change algorithm properties by calling:
+        * ClusterizerSetKMeansLimits() to change number of restarts or iterations
+        * ClusterizerSetKMeansInit() to change initialization algorithm
+
+        By  default,  one  restart  and  unlimited number of iterations are  used.
+        Initialization algorithm is chosen automatically.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        NOTE: k-means clustering  algorithm has two  phases:  selection of initial
+              centers and clustering  itself.  ALGLIB  parallelizes  both  phases.
+              Parallel version is optimized for the following  scenario: medium or
+              high-dimensional problem (8 or more dimensions) with large number of
+              points and clusters. However, some speed-up  can  be  obtained  even 
+              when assumptions above are violated.
+
+        INPUT PARAMETERS:
+            S       -   clusterizer state, initialized by ClusterizerCreate()
+            K       -   number of clusters, K>=0.
+                        K  can  be  zero only when algorithm is called  for  empty
+                        dataset,  in   this   case   completion  code  is  set  to
+                        success (+1).
+                        If  K=0  and  dataset  size  is  non-zero,  we   can   not
+                        meaningfully assign points to some center  (there  are  no
+                        centers because K=0) and  return  -3  as  completion  code
+                        (failure).
+
+        OUTPUT PARAMETERS:
+            Rep     -   clustering results; see description of KMeansReport
+                        structure for more information.
+
+        NOTE 1: k-means  clustering  can  be  performed  only  for  datasets  with
+                Euclidean  distance  function.  Algorithm  will  return   negative
+                completion code in Rep.TerminationType in case dataset  was  added
+                to clusterizer with DistType other than Euclidean (or dataset  was
+                specified by distance matrix instead of explicitly given points).
+                
+        NOTE 2: by default, k-means uses non-deterministic seed to initialize  RNG
+                which is used to select initial centers. As  result,  each  run of
+                algorithm may return different values. If you  need  deterministic
+                behavior, use ClusterizerSetSeed() function.
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizerrunkmeans(clusterizerstate s,
+            int k,
+            kmeansreport rep,
+            alglib.xparams _params)
+        {
+            double[,] dummy = new double[0,0];
+
+            alglib.ap.assert(k>=0, "ClusterizerRunKMeans: K<0");
+            
+            //
+            // Incorrect distance type
+            //
+            if( s.disttype!=2 )
+            {
+                rep.npoints = s.npoints;
+                rep.terminationtype = -5;
+                rep.k = k;
+                rep.iterationscount = 0;
+                rep.energy = 0.0;
+                return;
+            }
+            
+            //
+            // K>NPoints or (K=0 and NPoints>0)
+            //
+            if( k>s.npoints || (k==0 && s.npoints>0) )
+            {
+                rep.npoints = s.npoints;
+                rep.terminationtype = -3;
+                rep.k = k;
+                rep.iterationscount = 0;
+                rep.energy = 0.0;
+                return;
+            }
+            
+            //
+            // No points
+            //
+            if( s.npoints==0 )
+            {
+                rep.npoints = 0;
+                rep.terminationtype = 1;
+                rep.k = k;
+                rep.iterationscount = 0;
+                rep.energy = 0.0;
+                return;
+            }
+            
+            //
+            // Normal case:
+            // 1<=K<=NPoints, Euclidean distance 
+            //
+            rep.npoints = s.npoints;
+            rep.nfeatures = s.nfeatures;
+            rep.k = k;
+            rep.npoints = s.npoints;
+            rep.nfeatures = s.nfeatures;
+            kmeansgenerateinternal(s.xy, s.npoints, s.nfeatures, k, s.kmeansinitalgo, s.seed, s.kmeansmaxits, s.kmeansrestarts, s.kmeansdbgnoits, ref rep.terminationtype, ref rep.iterationscount, ref dummy, false, ref rep.c, true, ref rep.cidx, ref rep.energy, s.kmeanstmp, _params);
+        }
+
+
+        /*************************************************************************
+        This function returns distance matrix for dataset
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            XY      -   array[NPoints,NFeatures], dataset
+            NPoints -   number of points, >=0
+            NFeatures-  number of features, >=1
+            DistType-   distance function:
+                        *  0    Chebyshev distance  (L-inf norm)
+                        *  1    city block distance (L1 norm)
+                        *  2    Euclidean distance  (L2 norm, non-squared)
+                        * 10    Pearson correlation:
+                                dist(a,b) = 1-corr(a,b)
+                        * 11    Absolute Pearson correlation:
+                                dist(a,b) = 1-|corr(a,b)|
+                        * 12    Uncentered Pearson correlation (cosine of the angle):
+                                dist(a,b) = a'*b/(|a|*|b|)
+                        * 13    Absolute uncentered Pearson correlation
+                                dist(a,b) = |a'*b|/(|a|*|b|)
+                        * 20    Spearman rank correlation:
+                                dist(a,b) = 1-rankcorr(a,b)
+                        * 21    Absolute Spearman rank correlation
+                                dist(a,b) = 1-|rankcorr(a,b)|
+
+        OUTPUT PARAMETERS:
+            D       -   array[NPoints,NPoints], distance matrix
+                        (full matrix is returned, with lower and upper triangles)
+
+        NOTE:  different distance functions have different performance penalty:
+               * Euclidean or Pearson correlation distances are the fastest ones
+               * Spearman correlation distance function is a bit slower
+               * city block and Chebyshev distances are order of magnitude slower
+               
+               The reason behing difference in performance is that correlation-based
+               distance functions are computed using optimized linear algebra kernels,
+               while Chebyshev and city block distance functions are computed using
+               simple nested loops with two branches at each iteration.
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizergetdistances(double[,] xy,
+            int npoints,
+            int nfeatures,
+            int disttype,
+            ref double[,] d,
+            alglib.xparams _params)
+        {
+            apserv.apbuffers buf = new apserv.apbuffers();
+
+            d = new double[0,0];
+
+            alglib.ap.assert(nfeatures>=1, "ClusterizerGetDistances: NFeatures<1");
+            alglib.ap.assert(npoints>=0, "ClusterizerGetDistances: NPoints<1");
+            alglib.ap.assert((((((((disttype==0 || disttype==1) || disttype==2) || disttype==10) || disttype==11) || disttype==12) || disttype==13) || disttype==20) || disttype==21, "ClusterizerGetDistances: incorrect DistType");
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "ClusterizerGetDistances: Rows(XY)<NPoints");
+            alglib.ap.assert(alglib.ap.cols(xy)>=nfeatures, "ClusterizerGetDistances: Cols(XY)<NFeatures");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, npoints, nfeatures, _params), "ClusterizerGetDistances: XY contains NAN/INF");
+            clusterizergetdistancesbuf(buf, xy, npoints, nfeatures, disttype, ref d, _params);
+        }
+
+
+        /*************************************************************************
+        Buffered version  of  ClusterizerGetDistances()  which  reuses  previously
+        allocated space.
+
+          -- ALGLIB --
+             Copyright 29.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizergetdistancesbuf(apserv.apbuffers buf,
+            double[,] xy,
+            int npoints,
+            int nfeatures,
+            int disttype,
+            ref double[,] d,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double vv = 0;
+            double vr = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(nfeatures>=1, "ClusterizerGetDistancesBuf: NFeatures<1");
+            alglib.ap.assert(npoints>=0, "ClusterizerGetDistancesBuf: NPoints<1");
+            alglib.ap.assert((((((((disttype==0 || disttype==1) || disttype==2) || disttype==10) || disttype==11) || disttype==12) || disttype==13) || disttype==20) || disttype==21, "ClusterizerGetDistancesBuf: incorrect DistType");
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "ClusterizerGetDistancesBuf: Rows(XY)<NPoints");
+            alglib.ap.assert(alglib.ap.cols(xy)>=nfeatures, "ClusterizerGetDistancesBuf: Cols(XY)<NFeatures");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, npoints, nfeatures, _params), "ClusterizerGetDistancesBuf: XY contains NAN/INF");
+            
+            //
+            // Quick exit
+            //
+            if( npoints==0 )
+            {
+                return;
+            }
+            if( npoints==1 )
+            {
+                apserv.rmatrixsetlengthatleast(ref d, 1, 1, _params);
+                d[0,0] = 0;
+                return;
+            }
+            
+            //
+            // Build distance matrix D.
+            //
+            if( disttype==0 || disttype==1 )
+            {
+                
+                //
+                // Chebyshev or city-block distances:
+                // * recursively calculate upper triangle (with main diagonal)
+                // * copy it to the bottom part of the matrix
+                //
+                apserv.rmatrixsetlengthatleast(ref d, npoints, npoints, _params);
+                evaluatedistancematrixrec(xy, nfeatures, disttype, d, 0, npoints, 0, npoints, _params);
+                ablas.rmatrixenforcesymmetricity(d, npoints, true, _params);
+                return;
+            }
+            if( disttype==2 )
+            {
+                
+                //
+                // Euclidean distance
+                //
+                // NOTE: parallelization is done within RMatrixSYRK
+                //
+                apserv.rmatrixsetlengthatleast(ref d, npoints, npoints, _params);
+                apserv.rmatrixsetlengthatleast(ref buf.rm0, npoints, nfeatures, _params);
+                apserv.rvectorsetlengthatleast(ref buf.ra1, nfeatures, _params);
+                apserv.rvectorsetlengthatleast(ref buf.ra0, npoints, _params);
+                for(j=0; j<=nfeatures-1; j++)
+                {
+                    buf.ra1[j] = 0.0;
+                }
+                v = (double)1/(double)npoints;
+                for(i=0; i<=npoints-1; i++)
+                {
+                    for(i_=0; i_<=nfeatures-1;i_++)
+                    {
+                        buf.ra1[i_] = buf.ra1[i_] + v*xy[i,i_];
+                    }
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    for(i_=0; i_<=nfeatures-1;i_++)
+                    {
+                        buf.rm0[i,i_] = xy[i,i_];
+                    }
+                    for(i_=0; i_<=nfeatures-1;i_++)
+                    {
+                        buf.rm0[i,i_] = buf.rm0[i,i_] - buf.ra1[i_];
+                    }
+                }
+                ablas.rmatrixsyrk(npoints, nfeatures, 1.0, buf.rm0, 0, 0, 0, 0.0, d, 0, 0, true, _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    buf.ra0[i] = d[i,i];
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    d[i,i] = 0.0;
+                    for(j=i+1; j<=npoints-1; j++)
+                    {
+                        v = Math.Sqrt(Math.Max(buf.ra0[i]+buf.ra0[j]-2*d[i,j], 0.0));
+                        d[i,j] = v;
+                    }
+                }
+                ablas.rmatrixenforcesymmetricity(d, npoints, true, _params);
+                return;
+            }
+            if( disttype==10 || disttype==11 )
+            {
+                
+                //
+                // Absolute/nonabsolute Pearson correlation distance
+                //
+                // NOTE: parallelization is done within PearsonCorrM, which calls RMatrixSYRK internally
+                //
+                apserv.rmatrixsetlengthatleast(ref d, npoints, npoints, _params);
+                apserv.rvectorsetlengthatleast(ref buf.ra0, npoints, _params);
+                apserv.rmatrixsetlengthatleast(ref buf.rm0, npoints, nfeatures, _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    v = 0.0;
+                    for(j=0; j<=nfeatures-1; j++)
+                    {
+                        v = v+xy[i,j];
+                    }
+                    v = v/nfeatures;
+                    for(j=0; j<=nfeatures-1; j++)
+                    {
+                        buf.rm0[i,j] = xy[i,j]-v;
+                    }
+                }
+                ablas.rmatrixsyrk(npoints, nfeatures, 1.0, buf.rm0, 0, 0, 0, 0.0, d, 0, 0, true, _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    buf.ra0[i] = d[i,i];
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    d[i,i] = 0.0;
+                    for(j=i+1; j<=npoints-1; j++)
+                    {
+                        v = d[i,j]/Math.Sqrt(buf.ra0[i]*buf.ra0[j]);
+                        if( disttype==10 )
+                        {
+                            v = 1-v;
+                        }
+                        else
+                        {
+                            v = 1-Math.Abs(v);
+                        }
+                        v = Math.Max(v, 0.0);
+                        d[i,j] = v;
+                    }
+                }
+                ablas.rmatrixenforcesymmetricity(d, npoints, true, _params);
+                return;
+            }
+            if( disttype==12 || disttype==13 )
+            {
+                
+                //
+                // Absolute/nonabsolute uncentered Pearson correlation distance
+                //
+                // NOTE: parallelization is done within RMatrixSYRK
+                //
+                apserv.rmatrixsetlengthatleast(ref d, npoints, npoints, _params);
+                apserv.rvectorsetlengthatleast(ref buf.ra0, npoints, _params);
+                ablas.rmatrixsyrk(npoints, nfeatures, 1.0, xy, 0, 0, 0, 0.0, d, 0, 0, true, _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    buf.ra0[i] = d[i,i];
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    d[i,i] = 0.0;
+                    for(j=i+1; j<=npoints-1; j++)
+                    {
+                        v = d[i,j]/Math.Sqrt(buf.ra0[i]*buf.ra0[j]);
+                        if( disttype==13 )
+                        {
+                            v = Math.Abs(v);
+                        }
+                        v = Math.Min(v, 1.0);
+                        d[i,j] = 1-v;
+                    }
+                }
+                ablas.rmatrixenforcesymmetricity(d, npoints, true, _params);
+                return;
+            }
+            if( disttype==20 || disttype==21 )
+            {
+                
+                //
+                // Spearman rank correlation
+                //
+                // NOTE: parallelization of correlation matrix is done within
+                //       PearsonCorrM, which calls RMatrixSYRK internally
+                //
+                apserv.rmatrixsetlengthatleast(ref d, npoints, npoints, _params);
+                apserv.rvectorsetlengthatleast(ref buf.ra0, npoints, _params);
+                apserv.rmatrixsetlengthatleast(ref buf.rm0, npoints, nfeatures, _params);
+                ablas.rmatrixcopy(npoints, nfeatures, xy, 0, 0, buf.rm0, 0, 0, _params);
+                basestat.rankdatacentered(buf.rm0, npoints, nfeatures, _params);
+                ablas.rmatrixsyrk(npoints, nfeatures, 1.0, buf.rm0, 0, 0, 0, 0.0, d, 0, 0, true, _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    if( (double)(d[i,i])>(double)(0) )
+                    {
+                        buf.ra0[i] = 1/Math.Sqrt(d[i,i]);
+                    }
+                    else
+                    {
+                        buf.ra0[i] = 0.0;
+                    }
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    v = buf.ra0[i];
+                    d[i,i] = 0.0;
+                    for(j=i+1; j<=npoints-1; j++)
+                    {
+                        vv = d[i,j]*v*buf.ra0[j];
+                        if( disttype==20 )
+                        {
+                            vr = 1-vv;
+                        }
+                        else
+                        {
+                            vr = 1-Math.Abs(vv);
+                        }
+                        if( (double)(vr)<(double)(0) )
+                        {
+                            vr = 0.0;
+                        }
+                        d[i,j] = vr;
+                    }
+                }
+                ablas.rmatrixenforcesymmetricity(d, npoints, true, _params);
+                return;
+            }
+            alglib.ap.assert(false);
+        }
+
+
+        /*************************************************************************
+        This function takes as input clusterization report Rep,  desired  clusters
+        count K, and builds top K clusters from hierarchical clusterization  tree.
+        It returns assignment of points to clusters (array of cluster indexes).
+
+        INPUT PARAMETERS:
+            Rep     -   report from ClusterizerRunAHC() performed on XY
+            K       -   desired number of clusters, 1<=K<=NPoints.
+                        K can be zero only when NPoints=0.
+
+        OUTPUT PARAMETERS:
+            CIdx    -   array[NPoints], I-th element contains cluster index  (from
+                        0 to K-1) for I-th point of the dataset.
+            CZ      -   array[K]. This array allows  to  convert  cluster  indexes
+                        returned by this function to indexes used by  Rep.Z.  J-th
+                        cluster returned by this function corresponds to  CZ[J]-th
+                        cluster stored in Rep.Z/PZ/PM.
+                        It is guaranteed that CZ[I]<CZ[I+1].
+
+        NOTE: K clusters built by this subroutine are assumed to have no hierarchy.
+              Although  they  were  obtained  by  manipulation with top K nodes of
+              dendrogram  (i.e.  hierarchical  decomposition  of  dataset),   this
+              function does not return information about hierarchy.  Each  of  the
+              clusters stand on its own.
+              
+        NOTE: Cluster indexes returned by this function  does  not  correspond  to
+              indexes returned in Rep.Z/PZ/PM. Either you work  with  hierarchical
+              representation of the dataset (dendrogram), or you work with  "flat"
+              representation returned by this function.  Each  of  representations
+              has its own clusters indexing system (former uses [0, 2*NPoints-2]),
+              while latter uses [0..K-1]), although  it  is  possible  to  perform
+              conversion from one system to another by means of CZ array, returned
+              by this function, which allows you to convert indexes stored in CIdx
+              to the numeration system used by Rep.Z.
+              
+        NOTE: this subroutine is optimized for moderate values of K. Say, for  K=5
+              it will perform many times faster than  for  K=100.  Its  worst-case
+              performance is O(N*K), although in average case  it  perform  better
+              (up to O(N*log(K))).
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizergetkclusters(ahcreport rep,
+            int k,
+            ref int[] cidx,
+            ref int[] cz,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int mergeidx = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int t = 0;
+            bool[] presentclusters = new bool[0];
+            int[] clusterindexes = new int[0];
+            int[] clustersizes = new int[0];
+            int[] tmpidx = new int[0];
+            int npoints = 0;
+
+            cidx = new int[0];
+            cz = new int[0];
+
+            npoints = rep.npoints;
+            alglib.ap.assert(npoints>=0, "ClusterizerGetKClusters: internal error in Rep integrity");
+            alglib.ap.assert(k>=0, "ClusterizerGetKClusters: K<=0");
+            alglib.ap.assert(k<=npoints, "ClusterizerGetKClusters: K>NPoints");
+            alglib.ap.assert(k>0 || npoints==0, "ClusterizerGetKClusters: K<=0");
+            alglib.ap.assert(npoints==rep.npoints, "ClusterizerGetKClusters: NPoints<>Rep.NPoints");
+            
+            //
+            // Quick exit
+            //
+            if( npoints==0 )
+            {
+                return;
+            }
+            if( npoints==1 )
+            {
+                cz = new int[1];
+                cidx = new int[1];
+                cz[0] = 0;
+                cidx[0] = 0;
+                return;
+            }
+            
+            //
+            // Replay merges, from top to bottom,
+            // keep track of clusters being present at the moment
+            //
+            presentclusters = new bool[2*npoints-1];
+            tmpidx = new int[npoints];
+            for(i=0; i<=2*npoints-3; i++)
+            {
+                presentclusters[i] = false;
+            }
+            presentclusters[2*npoints-2] = true;
+            for(i=0; i<=npoints-1; i++)
+            {
+                tmpidx[i] = 2*npoints-2;
+            }
+            for(mergeidx=npoints-2; mergeidx>=npoints-k; mergeidx--)
+            {
+                
+                //
+                // Update information about clusters being present at the moment
+                //
+                presentclusters[npoints+mergeidx] = false;
+                presentclusters[rep.z[mergeidx,0]] = true;
+                presentclusters[rep.z[mergeidx,1]] = true;
+                
+                //
+                // Update TmpIdx according to the current state of the dataset
+                //
+                // NOTE: TmpIdx contains cluster indexes from [0..2*NPoints-2];
+                //       we will convert them to [0..K-1] later.
+                //
+                i0 = rep.pm[mergeidx,0];
+                i1 = rep.pm[mergeidx,1];
+                t = rep.z[mergeidx,0];
+                for(i=i0; i<=i1; i++)
+                {
+                    tmpidx[i] = t;
+                }
+                i0 = rep.pm[mergeidx,2];
+                i1 = rep.pm[mergeidx,3];
+                t = rep.z[mergeidx,1];
+                for(i=i0; i<=i1; i++)
+                {
+                    tmpidx[i] = t;
+                }
+            }
+            
+            //
+            // Fill CZ - array which allows us to convert cluster indexes
+            // from one system to another.
+            //
+            cz = new int[k];
+            clusterindexes = new int[2*npoints-1];
+            t = 0;
+            for(i=0; i<=2*npoints-2; i++)
+            {
+                if( presentclusters[i] )
+                {
+                    cz[t] = i;
+                    clusterindexes[i] = t;
+                    t = t+1;
+                }
+            }
+            alglib.ap.assert(t==k, "ClusterizerGetKClusters: internal error");
+            
+            //
+            // Convert indexes stored in CIdx
+            //
+            cidx = new int[npoints];
+            for(i=0; i<=npoints-1; i++)
+            {
+                cidx[i] = clusterindexes[tmpidx[rep.p[i]]];
+            }
+        }
+
+
+        /*************************************************************************
+        This  function  accepts  AHC  report  Rep,  desired  minimum  intercluster
+        distance and returns top clusters from  hierarchical  clusterization  tree
+        which are separated by distance R or HIGHER.
+
+        It returns assignment of points to clusters (array of cluster indexes).
+
+        There is one more function with similar name - ClusterizerSeparatedByCorr,
+        which returns clusters with intercluster correlation equal to R  or  LOWER
+        (note: higher for distance, lower for correlation).
+
+        INPUT PARAMETERS:
+            Rep     -   report from ClusterizerRunAHC() performed on XY
+            R       -   desired minimum intercluster distance, R>=0
+
+        OUTPUT PARAMETERS:
+            K       -   number of clusters, 1<=K<=NPoints
+            CIdx    -   array[NPoints], I-th element contains cluster index  (from
+                        0 to K-1) for I-th point of the dataset.
+            CZ      -   array[K]. This array allows  to  convert  cluster  indexes
+                        returned by this function to indexes used by  Rep.Z.  J-th
+                        cluster returned by this function corresponds to  CZ[J]-th
+                        cluster stored in Rep.Z/PZ/PM.
+                        It is guaranteed that CZ[I]<CZ[I+1].
+
+        NOTE: K clusters built by this subroutine are assumed to have no hierarchy.
+              Although  they  were  obtained  by  manipulation with top K nodes of
+              dendrogram  (i.e.  hierarchical  decomposition  of  dataset),   this
+              function does not return information about hierarchy.  Each  of  the
+              clusters stand on its own.
+              
+        NOTE: Cluster indexes returned by this function  does  not  correspond  to
+              indexes returned in Rep.Z/PZ/PM. Either you work  with  hierarchical
+              representation of the dataset (dendrogram), or you work with  "flat"
+              representation returned by this function.  Each  of  representations
+              has its own clusters indexing system (former uses [0, 2*NPoints-2]),
+              while latter uses [0..K-1]), although  it  is  possible  to  perform
+              conversion from one system to another by means of CZ array, returned
+              by this function, which allows you to convert indexes stored in CIdx
+              to the numeration system used by Rep.Z.
+              
+        NOTE: this subroutine is optimized for moderate values of K. Say, for  K=5
+              it will perform many times faster than  for  K=100.  Its  worst-case
+              performance is O(N*K), although in average case  it  perform  better
+              (up to O(N*log(K))).
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizerseparatedbydist(ahcreport rep,
+            double r,
+            ref int k,
+            ref int[] cidx,
+            ref int[] cz,
+            alglib.xparams _params)
+        {
+            k = 0;
+            cidx = new int[0];
+            cz = new int[0];
+
+            alglib.ap.assert(math.isfinite(r) && (double)(r)>=(double)(0), "ClusterizerSeparatedByDist: R is infinite or less than 0");
+            k = 1;
+            while( k<rep.npoints && (double)(rep.mergedist[rep.npoints-1-k])>=(double)(r) )
+            {
+                k = k+1;
+            }
+            clusterizergetkclusters(rep, k, ref cidx, ref cz, _params);
+        }
+
+
+        /*************************************************************************
+        This  function  accepts  AHC  report  Rep,  desired  maximum  intercluster
+        correlation and returns top clusters from hierarchical clusterization tree
+        which are separated by correlation R or LOWER.
+
+        It returns assignment of points to clusters (array of cluster indexes).
+
+        There is one more function with similar name - ClusterizerSeparatedByDist,
+        which returns clusters with intercluster distance equal  to  R  or  HIGHER
+        (note: higher for distance, lower for correlation).
+
+        INPUT PARAMETERS:
+            Rep     -   report from ClusterizerRunAHC() performed on XY
+            R       -   desired maximum intercluster correlation, -1<=R<=+1
+
+        OUTPUT PARAMETERS:
+            K       -   number of clusters, 1<=K<=NPoints
+            CIdx    -   array[NPoints], I-th element contains cluster index  (from
+                        0 to K-1) for I-th point of the dataset.
+            CZ      -   array[K]. This array allows  to  convert  cluster  indexes
+                        returned by this function to indexes used by  Rep.Z.  J-th
+                        cluster returned by this function corresponds to  CZ[J]-th
+                        cluster stored in Rep.Z/PZ/PM.
+                        It is guaranteed that CZ[I]<CZ[I+1].
+
+        NOTE: K clusters built by this subroutine are assumed to have no hierarchy.
+              Although  they  were  obtained  by  manipulation with top K nodes of
+              dendrogram  (i.e.  hierarchical  decomposition  of  dataset),   this
+              function does not return information about hierarchy.  Each  of  the
+              clusters stand on its own.
+              
+        NOTE: Cluster indexes returned by this function  does  not  correspond  to
+              indexes returned in Rep.Z/PZ/PM. Either you work  with  hierarchical
+              representation of the dataset (dendrogram), or you work with  "flat"
+              representation returned by this function.  Each  of  representations
+              has its own clusters indexing system (former uses [0, 2*NPoints-2]),
+              while latter uses [0..K-1]), although  it  is  possible  to  perform
+              conversion from one system to another by means of CZ array, returned
+              by this function, which allows you to convert indexes stored in CIdx
+              to the numeration system used by Rep.Z.
+              
+        NOTE: this subroutine is optimized for moderate values of K. Say, for  K=5
+              it will perform many times faster than  for  K=100.  Its  worst-case
+              performance is O(N*K), although in average case  it  perform  better
+              (up to O(N*log(K))).
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void clusterizerseparatedbycorr(ahcreport rep,
+            double r,
+            ref int k,
+            ref int[] cidx,
+            ref int[] cz,
+            alglib.xparams _params)
+        {
+            k = 0;
+            cidx = new int[0];
+            cz = new int[0];
+
+            alglib.ap.assert((math.isfinite(r) && (double)(r)>=(double)(-1)) && (double)(r)<=(double)(1), "ClusterizerSeparatedByCorr: R is infinite or less than 0");
+            k = 1;
+            while( k<rep.npoints && (double)(rep.mergedist[rep.npoints-1-k])>=(double)(1-r) )
+            {
+                k = k+1;
+            }
+            clusterizergetkclusters(rep, k, ref cidx, ref cz, _params);
+        }
+
+
+        /*************************************************************************
+        K-means++ initialization
+
+        INPUT PARAMETERS:
+            Buf         -   special reusable structure which stores previously allocated
+                            memory, intended to avoid memory fragmentation when solving
+                            multiple subsequent problems. Must be initialized prior to
+                            usage.
+
+        OUTPUT PARAMETERS:
+            Buf         -   initialized structure
+
+          -- ALGLIB --
+             Copyright 24.07.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kmeansinitbuf(kmeansbuffers buf,
+            alglib.xparams _params)
+        {
+            apserv.apbuffers updateseed = new apserv.apbuffers();
+
+            alglib.smp.ae_shared_pool_set_seed(buf.updatepool, updateseed);
+        }
+
+
+        /*************************************************************************
+        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
+            InitAlgo    -   initialization algorithm:
+                            * 0 - automatic selection of best algorithm
+                            * 1 - random selection of centers
+                            * 2 - k-means++
+                            * 3 - fast-greedy init
+                            *-1 - first K rows of dataset are used
+                                  (special debug algorithm)
+            Seed        -   seed value for internal RNG:
+                            * positive value is used to initialize RNG in order to
+                              induce deterministic behavior of algorithm
+                            * zero or negative value means  that random  seed   is
+                              generated
+            MaxIts      -   iterations limit or zero for no limit
+            Restarts    -   number of restarts, Restarts>=1
+            KMeansDbgNoIts- debug flag; if set, Lloyd's iteration is not performed,
+                            only initialization phase.
+            Buf         -   special reusable structure which stores previously allocated
+                            memory, intended to avoid memory fragmentation when solving
+                            multiple subsequent problems:
+                            * MUST BE INITIALIZED WITH KMeansInitBuffers() CALL BEFORE
+                              FIRST PASS TO THIS FUNCTION!
+                            * subsequent passes must be made without re-initialization
+
+        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
+            IterationsCount- actual number of iterations performed by clusterizer
+            CCol        -   array[0..NVars-1,0..K-1].matrix whose columns store
+                            cluster's centers
+            NeedCCol    -   True in case caller requires to store result in CCol
+            CRow        -   array[0..K-1,0..NVars-1], same as CCol, but centers are
+                            stored in rows
+            NeedCRow    -   True in case caller requires to store result in CCol
+            XYC         -   array[NPoints], which contains cluster indexes
+            Energy      -   merit function of clusterization
+
+          -- ALGLIB --
+             Copyright 21.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kmeansgenerateinternal(double[,] xy,
+            int npoints,
+            int nvars,
+            int k,
+            int initalgo,
+            int seed,
+            int maxits,
+            int restarts,
+            bool kmeansdbgnoits,
+            ref int info,
+            ref int iterationscount,
+            ref double[,] ccol,
+            bool needccol,
+            ref double[,] crow,
+            bool needcrow,
+            ref int[] xyc,
+            ref double energy,
+            kmeansbuffers buf,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int i1 = 0;
+            double e = 0;
+            double eprev = 0;
+            double v = 0;
+            double vv = 0;
+            bool waschanges = new bool();
+            bool zerosizeclusters = new bool();
+            int pass = 0;
+            int itcnt = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+            int i_ = 0;
+
+            info = 0;
+            iterationscount = 0;
+            ccol = new double[0,0];
+            crow = new double[0,0];
+            xyc = new int[0];
+            energy = 0;
+
+            
+            //
+            // Test parameters
+            //
+            if( ((npoints<k || nvars<1) || k<1) || restarts<1 )
+            {
+                info = -1;
+                iterationscount = 0;
+                return;
+            }
+            
+            //
+            // TODO: special case K=1
+            // TODO: special case K=NPoints
+            //
+            info = 1;
+            iterationscount = 0;
+            
+            //
+            // Multiple passes of k-means++ algorithm
+            //
+            if( seed<=0 )
+            {
+                hqrnd.hqrndrandomize(rs, _params);
+            }
+            else
+            {
+                hqrnd.hqrndseed(325355, seed, rs, _params);
+            }
+            xyc = new int[npoints];
+            apserv.rmatrixsetlengthatleast(ref buf.ct, k, nvars, _params);
+            apserv.rmatrixsetlengthatleast(ref buf.ctbest, k, nvars, _params);
+            apserv.ivectorsetlengthatleast(ref buf.xycprev, npoints, _params);
+            apserv.ivectorsetlengthatleast(ref buf.xycbest, npoints, _params);
+            apserv.rvectorsetlengthatleast(ref buf.d2, npoints, _params);
+            apserv.ivectorsetlengthatleast(ref buf.csizes, k, _params);
+            energy = math.maxrealnumber;
+            for(pass=1; pass<=restarts; pass++)
+            {
+                
+                //
+                // Select initial centers.
+                //
+                // 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.
+                //
+                // Also note that SelectInitialCenters() may return degenerate set of centers
+                // (some of them have no corresponding points in dataset, some are non-distinct).
+                // Algorithm below is robust enough to deal with such set.
+                //
+                selectinitialcenters(xy, npoints, nvars, initalgo, rs, k, ref buf.ct, buf.initbuf, buf.updatepool, _params);
+                
+                //
+                // Lloyd's iteration
+                //
+                if( !kmeansdbgnoits )
+                {
+                    
+                    //
+                    // Perform iteration as usual, in normal mode
+                    //
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        xyc[i] = -1;
+                    }
+                    eprev = math.maxrealnumber;
+                    e = math.maxrealnumber;
+                    itcnt = 0;
+                    while( maxits==0 || itcnt<maxits )
+                    {
+                        
+                        //
+                        // Update iteration counter
+                        //
+                        itcnt = itcnt+1;
+                        apserv.inc(ref iterationscount, _params);
+                        
+                        //
+                        // Call KMeansUpdateDistances(), fill XYC with center numbers,
+                        // D2 with center distances.
+                        //
+                        for(i=0; i<=npoints-1; i++)
+                        {
+                            buf.xycprev[i] = xyc[i];
+                        }
+                        kmeansupdatedistances(xy, 0, npoints, nvars, buf.ct, 0, k, xyc, buf.d2, buf.updatepool, _params);
+                        waschanges = false;
+                        for(i=0; i<=npoints-1; i++)
+                        {
+                            waschanges = waschanges || xyc[i]!=buf.xycprev[i];
+                        }
+                        
+                        //
+                        // Update centers
+                        //
+                        for(j=0; j<=k-1; j++)
+                        {
+                            buf.csizes[j] = 0;
+                        }
+                        for(i=0; i<=k-1; i++)
+                        {
+                            for(j=0; j<=nvars-1; j++)
+                            {
+                                buf.ct[i,j] = 0;
+                            }
+                        }
+                        for(i=0; i<=npoints-1; i++)
+                        {
+                            buf.csizes[xyc[i]] = buf.csizes[xyc[i]]+1;
+                            for(i_=0; i_<=nvars-1;i_++)
+                            {
+                                buf.ct[xyc[i],i_] = buf.ct[xyc[i],i_] + xy[i,i_];
+                            }
+                        }
+                        zerosizeclusters = false;
+                        for(j=0; j<=k-1; j++)
+                        {
+                            if( buf.csizes[j]!=0 )
+                            {
+                                v = (double)1/(double)buf.csizes[j];
+                                for(i_=0; i_<=nvars-1;i_++)
+                                {
+                                    buf.ct[j,i_] = v*buf.ct[j,i_];
+                                }
+                            }
+                            zerosizeclusters = zerosizeclusters || buf.csizes[j]==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, decrementing iteration counter
+                            // in order to allow one more iteration (this one was useless
+                            // and should not be counted).
+                            //
+                            if( !fixcenters(xy, npoints, nvars, buf.ct, k, buf.initbuf, buf.updatepool, _params) )
+                            {
+                                info = -3;
+                                return;
+                            }
+                            itcnt = itcnt-1;
+                            continue;
+                        }
+                        
+                        //
+                        // Stop if one of two conditions is met:
+                        // 1. nothing has changed during iteration
+                        // 2. energy function increased after recalculation on new centers
+                        //
+                        e = 0;
+                        for(i=0; i<=npoints-1; i++)
+                        {
+                            v = 0.0;
+                            i1 = xyc[i];
+                            for(j=0; j<=nvars-1; j++)
+                            {
+                                vv = xy[i,j]-buf.ct[i1,j];
+                                v = v+vv*vv;
+                            }
+                            e = e+v;
+                        }
+                        if( !waschanges || (double)(e)>=(double)(eprev) )
+                        {
+                            break;
+                        }
+                        
+                        //
+                        // Update EPrev
+                        //
+                        eprev = e;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Debug mode: no Lloyd's iteration.
+                    // We just calculate potential E.
+                    //
+                    kmeansupdatedistances(xy, 0, npoints, nvars, buf.ct, 0, k, xyc, buf.d2, buf.updatepool, _params);
+                    e = 0;
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        e = e+buf.d2[i];
+                    }
+                }
+                
+                //
+                // Compare E with best centers found so far
+                //
+                if( (double)(e)<(double)(energy) )
+                {
+                    
+                    //
+                    // store partition.
+                    //
+                    energy = e;
+                    blas.copymatrix(buf.ct, 0, k-1, 0, nvars-1, ref buf.ctbest, 0, k-1, 0, nvars-1, _params);
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        buf.xycbest[i] = xyc[i];
+                    }
+                }
+            }
+            
+            //
+            // Copy and transpose
+            //
+            if( needccol )
+            {
+                ccol = new double[nvars, k];
+                blas.copyandtranspose(buf.ctbest, 0, k-1, 0, nvars-1, ref ccol, 0, nvars-1, 0, k-1, _params);
+            }
+            if( needcrow )
+            {
+                crow = new double[k, nvars];
+                ablas.rmatrixcopy(k, nvars, buf.ctbest, 0, 0, crow, 0, 0, _params);
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                xyc[i] = buf.xycbest[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This procedure recalculates distances from points to centers  and  assigns
+        each point to closest center.
+
+        INPUT PARAMETERS:
+            XY          -   dataset, array [0..NPoints-1,0..NVars-1].
+            Idx0,Idx1   -   define range of dataset [Idx0,Idx1) to process;
+                            right boundary is not included.
+            NVars       -   number of variables, NVars>=1
+            CT          -   matrix of centers, centers are stored in rows
+            CIdx0,CIdx1 -   define range of centers [CIdx0,CIdx1) to process;
+                            right boundary is not included.
+            XYC         -   preallocated output buffer, 
+            XYDist2     -   preallocated output buffer
+            Tmp         -   temporary buffer, automatically reallocated if needed
+            BufferPool  -   shared pool seeded with instance of APBuffers structure
+                            (seed instance can be unitialized). It is recommended
+                            to use this pool only with KMeansUpdateDistances()
+                            function.
+
+        OUTPUT PARAMETERS:
+            XYC         -   new assignment of points to centers are stored
+                            in [Idx0,Idx1)
+            XYDist2     -   squared distances from points to their centers are
+                            stored in [Idx0,Idx1)
+
+          -- ALGLIB --
+             Copyright 21.01.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void kmeansupdatedistances(double[,] xy,
+            int idx0,
+            int idx1,
+            int nvars,
+            double[,] ct,
+            int cidx0,
+            int cidx1,
+            int[] xyc,
+            double[] xydist2,
+            alglib.smp.shared_pool bufferpool,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int j = 0;
+            int cclosest = 0;
+            double dclosest = 0;
+            double vv = 0;
+            apserv.apbuffers buf = null;
+            double rcomplexity = 0;
+            int task0 = 0;
+            int task1 = 0;
+            int pblkcnt = 0;
+            int cblkcnt = 0;
+            int vblkcnt = 0;
+            int pblk = 0;
+            int cblk = 0;
+            int vblk = 0;
+            int p0 = 0;
+            int p1 = 0;
+            int c0 = 0;
+            int c1 = 0;
+            int v0 = 0;
+            int v1 = 0;
+            double v00 = 0;
+            double v01 = 0;
+            double v10 = 0;
+            double v11 = 0;
+            double vp0 = 0;
+            double vp1 = 0;
+            double vc0 = 0;
+            double vc1 = 0;
+            int pcnt = 0;
+            int pcntpadded = 0;
+            int ccnt = 0;
+            int ccntpadded = 0;
+            int offs0 = 0;
+            int offs00 = 0;
+            int offs01 = 0;
+            int offs10 = 0;
+            int offs11 = 0;
+            int vcnt = 0;
+            int stride = 0;
+
+            
+            //
+            // Quick exit for special cases
+            //
+            if( idx1<=idx0 )
+            {
+                return;
+            }
+            if( cidx1<=cidx0 )
+            {
+                return;
+            }
+            if( nvars<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Try to recursively divide/process dataset
+            //
+            // NOTE: real arithmetics is used to avoid integer overflow on large problem sizes
+            //
+            rcomplexity = 2*apserv.rmul3(idx1-idx0, cidx1-cidx0, nvars, _params);
+            if( (double)(rcomplexity)>=(double)(apserv.smpactivationlevel(_params)) && idx1-idx0>=2*kmeansblocksize )
+            {
+                if( _trypexec_kmeansupdatedistances(xy,idx0,idx1,nvars,ct,cidx0,cidx1,xyc,xydist2,bufferpool, _params) )
+                {
+                    return;
+                }
+            }
+            if( (((double)(rcomplexity)>=(double)(apserv.spawnlevel(_params)) && idx1-idx0>=2*kmeansblocksize) && nvars>=kmeansparalleldim) && cidx1-cidx0>=kmeansparallelk )
+            {
+                apserv.splitlength(idx1-idx0, kmeansblocksize, ref task0, ref task1, _params);
+                kmeansupdatedistances(xy, idx0, idx0+task0, nvars, ct, cidx0, cidx1, xyc, xydist2, bufferpool, _params);
+                kmeansupdatedistances(xy, idx0+task0, idx1, nvars, ct, cidx0, cidx1, xyc, xydist2, bufferpool, _params);
+                return;
+            }
+            
+            //
+            // Dataset chunk is selected.
+            // 
+            // Process it with blocked algorithm:
+            // * iterate over points, process them in KMeansBlockSize-ed chunks
+            // * for each chunk of dataset, iterate over centers, process them in KMeansBlockSize-ed chunks
+            // * for each chunk of dataset/centerset, iterate over variables, process them in KMeansBlockSize-ed chunks
+            //
+            alglib.ap.assert(kmeansblocksize%2==0, "KMeansUpdateDistances: internal error");
+            alglib.smp.ae_shared_pool_retrieve(bufferpool, ref buf);
+            apserv.rvectorsetlengthatleast(ref buf.ra0, kmeansblocksize*kmeansblocksize, _params);
+            apserv.rvectorsetlengthatleast(ref buf.ra1, kmeansblocksize*kmeansblocksize, _params);
+            apserv.rvectorsetlengthatleast(ref buf.ra2, kmeansblocksize*kmeansblocksize, _params);
+            apserv.rvectorsetlengthatleast(ref buf.ra3, kmeansblocksize, _params);
+            apserv.ivectorsetlengthatleast(ref buf.ia3, kmeansblocksize, _params);
+            pblkcnt = apserv.chunkscount(idx1-idx0, kmeansblocksize, _params);
+            cblkcnt = apserv.chunkscount(cidx1-cidx0, kmeansblocksize, _params);
+            vblkcnt = apserv.chunkscount(nvars, kmeansblocksize, _params);
+            for(pblk=0; pblk<=pblkcnt-1; pblk++)
+            {
+                
+                //
+                // Process PBlk-th chunk of dataset.
+                //
+                p0 = idx0+pblk*kmeansblocksize;
+                p1 = Math.Min(p0+kmeansblocksize, idx1);
+                
+                //
+                // Prepare RA3[]/IA3[] for storage of best distances and best cluster numbers.
+                //
+                for(i=0; i<=kmeansblocksize-1; i++)
+                {
+                    buf.ra3[i] = math.maxrealnumber;
+                    buf.ia3[i] = -1;
+                }
+                
+                //
+                // Iterare over chunks of centerset.
+                //
+                for(cblk=0; cblk<=cblkcnt-1; cblk++)
+                {
+                    
+                    //
+                    // Process CBlk-th chunk of centerset
+                    //
+                    c0 = cidx0+cblk*kmeansblocksize;
+                    c1 = Math.Min(c0+kmeansblocksize, cidx1);
+                    
+                    //
+                    // At this point we have to calculate a set of pairwise distances
+                    // between points [P0,P1) and centers [C0,C1) and select best center
+                    // for each point. It can also be done with blocked algorithm
+                    // (blocking for variables).
+                    //
+                    // Following arrays are used:
+                    // * RA0[] - matrix of distances, padded by zeros for even size,
+                    //           rows are stored with stride KMeansBlockSize.
+                    // * RA1[] - matrix of points (variables corresponding to current
+                    //           block are extracted), padded by zeros for even size,
+                    //           rows are stored with stride KMeansBlockSize.
+                    // * RA2[] - matrix of centers (variables corresponding to current
+                    //           block are extracted), padded by zeros for even size,
+                    //           rows are stored with stride KMeansBlockSize.
+                    //
+                    //
+                    pcnt = p1-p0;
+                    pcntpadded = pcnt+pcnt%2;
+                    ccnt = c1-c0;
+                    ccntpadded = ccnt+ccnt%2;
+                    stride = kmeansblocksize;
+                    alglib.ap.assert(pcntpadded<=kmeansblocksize, "KMeansUpdateDistances: integrity error");
+                    alglib.ap.assert(ccntpadded<=kmeansblocksize, "KMeansUpdateDistances: integrity error");
+                    for(i=0; i<=pcntpadded-1; i++)
+                    {
+                        for(j=0; j<=ccntpadded-1; j++)
+                        {
+                            buf.ra0[i*stride+j] = 0.0;
+                        }
+                    }
+                    for(vblk=0; vblk<=vblkcnt-1; vblk++)
+                    {
+                        
+                        //
+                        // Fetch VBlk-th block of variables to arrays RA1 (points) and RA2 (centers).
+                        // Pad points and centers with zeros.
+                        //
+                        v0 = vblk*kmeansblocksize;
+                        v1 = Math.Min(v0+kmeansblocksize, nvars);
+                        vcnt = v1-v0;
+                        for(i=0; i<=pcnt-1; i++)
+                        {
+                            for(j=0; j<=vcnt-1; j++)
+                            {
+                                buf.ra1[i*stride+j] = xy[p0+i,v0+j];
+                            }
+                        }
+                        for(i=pcnt; i<=pcntpadded-1; i++)
+                        {
+                            for(j=0; j<=vcnt-1; j++)
+                            {
+                                buf.ra1[i*stride+j] = 0.0;
+                            }
+                        }
+                        for(i=0; i<=ccnt-1; i++)
+                        {
+                            for(j=0; j<=vcnt-1; j++)
+                            {
+                                buf.ra2[i*stride+j] = ct[c0+i,v0+j];
+                            }
+                        }
+                        for(i=ccnt; i<=ccntpadded-1; i++)
+                        {
+                            for(j=0; j<=vcnt-1; j++)
+                            {
+                                buf.ra2[i*stride+j] = 0.0;
+                            }
+                        }
+                        
+                        //
+                        // Update distance matrix with sums-of-squared-differences of RA1 and RA2
+                        //
+                        i0 = 0;
+                        while( i0<pcntpadded )
+                        {
+                            i1 = 0;
+                            while( i1<ccntpadded )
+                            {
+                                offs0 = i0*stride+i1;
+                                v00 = buf.ra0[offs0];
+                                v01 = buf.ra0[offs0+1];
+                                v10 = buf.ra0[offs0+stride];
+                                v11 = buf.ra0[offs0+stride+1];
+                                offs00 = i0*stride;
+                                offs01 = offs00+stride;
+                                offs10 = i1*stride;
+                                offs11 = offs10+stride;
+                                for(j=0; j<=vcnt-1; j++)
+                                {
+                                    vp0 = buf.ra1[offs00+j];
+                                    vp1 = buf.ra1[offs01+j];
+                                    vc0 = buf.ra2[offs10+j];
+                                    vc1 = buf.ra2[offs11+j];
+                                    vv = vp0-vc0;
+                                    v00 = v00+vv*vv;
+                                    vv = vp0-vc1;
+                                    v01 = v01+vv*vv;
+                                    vv = vp1-vc0;
+                                    v10 = v10+vv*vv;
+                                    vv = vp1-vc1;
+                                    v11 = v11+vv*vv;
+                                }
+                                offs0 = i0*stride+i1;
+                                buf.ra0[offs0] = v00;
+                                buf.ra0[offs0+1] = v01;
+                                buf.ra0[offs0+stride] = v10;
+                                buf.ra0[offs0+stride+1] = v11;
+                                i1 = i1+2;
+                            }
+                            i0 = i0+2;
+                        }
+                    }
+                    for(i=0; i<=pcnt-1; i++)
+                    {
+                        cclosest = buf.ia3[i];
+                        dclosest = buf.ra3[i];
+                        for(j=0; j<=ccnt-1; j++)
+                        {
+                            if( (double)(buf.ra0[i*stride+j])<(double)(dclosest) )
+                            {
+                                dclosest = buf.ra0[i*stride+j];
+                                cclosest = c0+j;
+                            }
+                        }
+                        buf.ia3[i] = cclosest;
+                        buf.ra3[i] = dclosest;
+                    }
+                }
+                
+                //
+                // Store best centers to XYC[]
+                //
+                for(i=p0; i<=p1-1; i++)
+                {
+                    xyc[i] = buf.ia3[i-p0];
+                    xydist2[i] = buf.ra3[i-p0];
+                }
+            }
+            alglib.smp.ae_shared_pool_recycle(bufferpool, ref buf);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_kmeansupdatedistances(double[,] xy,
+            int idx0,
+            int idx1,
+            int nvars,
+            double[,] ct,
+            int cidx0,
+            int cidx1,
+            int[] xyc,
+            double[] xydist2,
+            alglib.smp.shared_pool bufferpool, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        This function selects initial centers according to specified initialization
+        algorithm.
+
+        IMPORTANT: this function provides no  guarantees  regarding  selection  of
+                   DIFFERENT  centers.  Centers  returned  by  this  function  may
+                   include duplicates (say, when random sampling is  used). It  is
+                   also possible that some centers are empty.
+                   Algorithm which uses this function must be able to deal with it.
+                   Say, you may want to use FixCenters() in order to fix empty centers.
+
+        INPUT PARAMETERS:
+            XY          -   dataset, array [0..NPoints-1,0..NVars-1].
+            NPoints     -   points count
+            NVars       -   number of variables, NVars>=1
+            InitAlgo    -   initialization algorithm:
+                            * 0 - automatic selection of best algorithm
+                            * 1 - random selection
+                            * 2 - k-means++
+                            * 3 - fast-greedy init
+                            *-1 - first K rows of dataset are used (debug algorithm)
+            RS          -   RNG used to select centers
+            K           -   number of centers, K>=1
+            CT          -   possibly preallocated output buffer, resized if needed
+            InitBuf     -   internal buffer, possibly unitialized instance of
+                            APBuffers. It is recommended to use this instance only
+                            with SelectInitialCenters() and FixCenters() functions,
+                            because these functions may allocate really large storage.
+            UpdatePool  -   shared pool seeded with instance of APBuffers structure
+                            (seed instance can be unitialized). Used internally with
+                            KMeansUpdateDistances() function. It is recommended
+                            to use this pool ONLY with KMeansUpdateDistances()
+                            function.
+
+        OUTPUT PARAMETERS:
+            CT          -   set of K clusters, one per row
+            
+        RESULT:
+            True on success, False on failure (impossible to create K independent clusters)
+
+          -- ALGLIB --
+             Copyright 21.01.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static void selectinitialcenters(double[,] xy,
+            int npoints,
+            int nvars,
+            int initalgo,
+            hqrnd.hqrndstate rs,
+            int k,
+            ref double[,] ct,
+            apserv.apbuffers initbuf,
+            alglib.smp.shared_pool updatepool,
+            alglib.xparams _params)
+        {
+            int cidx = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double vv = 0;
+            double s = 0;
+            int lastnz = 0;
+            int ptidx = 0;
+            int samplesize = 0;
+            int samplescntnew = 0;
+            int samplescntall = 0;
+            double samplescale = 0;
+            int i_ = 0;
+
+            
+            //
+            // Check parameters
+            //
+            alglib.ap.assert(npoints>0, "SelectInitialCenters: internal error");
+            alglib.ap.assert(nvars>0, "SelectInitialCenters: internal error");
+            alglib.ap.assert(k>0, "SelectInitialCenters: internal error");
+            if( initalgo==0 )
+            {
+                initalgo = 3;
+            }
+            apserv.rmatrixsetlengthatleast(ref ct, k, nvars, _params);
+            
+            //
+            // Random initialization
+            //
+            if( initalgo==-1 )
+            {
+                for(i=0; i<=k-1; i++)
+                {
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        ct[i,i_] = xy[i%npoints,i_];
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Random initialization
+            //
+            if( initalgo==1 )
+            {
+                for(i=0; i<=k-1; i++)
+                {
+                    j = hqrnd.hqrnduniformi(rs, npoints, _params);
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        ct[i,i_] = xy[j,i_];
+                    }
+                }
+                return;
+            }
+            
+            //
+            // k-means++ initialization
+            //
+            if( initalgo==2 )
+            {
+                
+                //
+                // Prepare distances array.
+                // Select initial center at random.
+                //
+                apserv.rvectorsetlengthatleast(ref initbuf.ra0, npoints, _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    initbuf.ra0[i] = math.maxrealnumber;
+                }
+                ptidx = hqrnd.hqrnduniformi(rs, npoints, _params);
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    ct[0,i_] = xy[ptidx,i_];
+                }
+                
+                //
+                // For each newly added center repeat:
+                // * reevaluate distances from points to best centers
+                // * sample points with probability dependent on distance
+                // * add new center
+                //
+                for(cidx=0; cidx<=k-2; cidx++)
+                {
+                    
+                    //
+                    // Reevaluate distances
+                    //
+                    s = 0.0;
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        v = 0.0;
+                        for(j=0; j<=nvars-1; j++)
+                        {
+                            vv = xy[i,j]-ct[cidx,j];
+                            v = v+vv*vv;
+                        }
+                        if( (double)(v)<(double)(initbuf.ra0[i]) )
+                        {
+                            initbuf.ra0[i] = v;
+                        }
+                        s = s+initbuf.ra0[i];
+                    }
+                    
+                    //
+                    // If all distances are zero, it means that we can not find enough
+                    // distinct points. In this case we just select non-distinct center
+                    // at random and continue iterations. This issue will be handled
+                    // later in the FixCenters() function.
+                    //
+                    if( (double)(s)==(double)(0.0) )
+                    {
+                        ptidx = hqrnd.hqrnduniformi(rs, npoints, _params);
+                        for(i_=0; i_<=nvars-1;i_++)
+                        {
+                            ct[cidx+1,i_] = xy[ptidx,i_];
+                        }
+                        continue;
+                    }
+                    
+                    //
+                    // Select point as center using its distance.
+                    // We also handle situation when because of rounding errors
+                    // no point was selected - in this case, last non-zero one
+                    // will be used.
+                    //
+                    v = hqrnd.hqrnduniformr(rs, _params);
+                    vv = 0.0;
+                    lastnz = -1;
+                    ptidx = -1;
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        if( (double)(initbuf.ra0[i])==(double)(0.0) )
+                        {
+                            continue;
+                        }
+                        lastnz = i;
+                        vv = vv+initbuf.ra0[i];
+                        if( (double)(v)<=(double)(vv/s) )
+                        {
+                            ptidx = i;
+                            break;
+                        }
+                    }
+                    alglib.ap.assert(lastnz>=0, "SelectInitialCenters: integrity error");
+                    if( ptidx<0 )
+                    {
+                        ptidx = lastnz;
+                    }
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        ct[cidx+1,i_] = xy[ptidx,i_];
+                    }
+                }
+                return;
+            }
+            
+            //
+            // "Fast-greedy" algorithm based on "Scalable k-means++".
+            //
+            // We perform several rounds, within each round we sample about 0.5*K points
+            // (not exactly 0.5*K) until we have 2*K points sampled. Before each round
+            // we calculate distances from dataset points to closest points sampled so far.
+            // We sample dataset points independently using distance xtimes 0.5*K divided by total
+            // as probability (similar to k-means++, but each point is sampled independently;
+            // after each round we have roughtly 0.5*K points added to sample).
+            //
+            // After sampling is done, we run "greedy" version of k-means++ on this subsample
+            // which selects most distant point on every round.
+            //
+            if( initalgo==3 )
+            {
+                
+                //
+                // Prepare arrays.
+                // Select initial center at random, add it to "new" part of sample,
+                // which is stored at the beginning of the array
+                //
+                samplesize = 2*k;
+                samplescale = 0.5*k;
+                apserv.rmatrixsetlengthatleast(ref initbuf.rm0, samplesize, nvars, _params);
+                ptidx = hqrnd.hqrnduniformi(rs, npoints, _params);
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    initbuf.rm0[0,i_] = xy[ptidx,i_];
+                }
+                samplescntnew = 1;
+                samplescntall = 1;
+                apserv.rvectorsetlengthatleast(ref initbuf.ra0, npoints, _params);
+                apserv.rvectorsetlengthatleast(ref initbuf.ra1, npoints, _params);
+                apserv.ivectorsetlengthatleast(ref initbuf.ia1, npoints, _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    initbuf.ra0[i] = math.maxrealnumber;
+                }
+                
+                //
+                // Repeat until samples count is 2*K
+                //
+                while( samplescntall<samplesize )
+                {
+                    
+                    //
+                    // Evaluate distances from points to NEW centers, store to RA1.
+                    // Reset counter of "new" centers.
+                    //
+                    kmeansupdatedistances(xy, 0, npoints, nvars, initbuf.rm0, samplescntall-samplescntnew, samplescntall, initbuf.ia1, initbuf.ra1, updatepool, _params);
+                    samplescntnew = 0;
+                    
+                    //
+                    // Merge new distances with old ones.
+                    // Calculate sum of distances, if sum is exactly zero - fill sample
+                    // by randomly selected points and terminate.
+                    //
+                    s = 0.0;
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        initbuf.ra0[i] = Math.Min(initbuf.ra0[i], initbuf.ra1[i]);
+                        s = s+initbuf.ra0[i];
+                    }
+                    if( (double)(s)==(double)(0.0) )
+                    {
+                        while( samplescntall<samplesize )
+                        {
+                            ptidx = hqrnd.hqrnduniformi(rs, npoints, _params);
+                            for(i_=0; i_<=nvars-1;i_++)
+                            {
+                                initbuf.rm0[samplescntall,i_] = xy[ptidx,i_];
+                            }
+                            apserv.inc(ref samplescntall, _params);
+                            apserv.inc(ref samplescntnew, _params);
+                        }
+                        break;
+                    }
+                    
+                    //
+                    // Sample points independently.
+                    //
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        if( samplescntall==samplesize )
+                        {
+                            break;
+                        }
+                        if( (double)(initbuf.ra0[i])==(double)(0.0) )
+                        {
+                            continue;
+                        }
+                        if( (double)(hqrnd.hqrnduniformr(rs, _params))<=(double)(samplescale*initbuf.ra0[i]/s) )
+                        {
+                            for(i_=0; i_<=nvars-1;i_++)
+                            {
+                                initbuf.rm0[samplescntall,i_] = xy[i,i_];
+                            }
+                            apserv.inc(ref samplescntall, _params);
+                            apserv.inc(ref samplescntnew, _params);
+                        }
+                    }
+                }
+                
+                //
+                // Run greedy version of k-means on sampled points
+                //
+                apserv.rvectorsetlengthatleast(ref initbuf.ra0, samplescntall, _params);
+                for(i=0; i<=samplescntall-1; i++)
+                {
+                    initbuf.ra0[i] = math.maxrealnumber;
+                }
+                ptidx = hqrnd.hqrnduniformi(rs, samplescntall, _params);
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    ct[0,i_] = initbuf.rm0[ptidx,i_];
+                }
+                for(cidx=0; cidx<=k-2; cidx++)
+                {
+                    
+                    //
+                    // Reevaluate distances
+                    //
+                    for(i=0; i<=samplescntall-1; i++)
+                    {
+                        v = 0.0;
+                        for(j=0; j<=nvars-1; j++)
+                        {
+                            vv = initbuf.rm0[i,j]-ct[cidx,j];
+                            v = v+vv*vv;
+                        }
+                        if( (double)(v)<(double)(initbuf.ra0[i]) )
+                        {
+                            initbuf.ra0[i] = v;
+                        }
+                    }
+                    
+                    //
+                    // Select point as center in greedy manner - most distant
+                    // point is selected.
+                    //
+                    ptidx = 0;
+                    for(i=0; i<=samplescntall-1; i++)
+                    {
+                        if( (double)(initbuf.ra0[i])>(double)(initbuf.ra0[ptidx]) )
+                        {
+                            ptidx = i;
+                        }
+                    }
+                    for(i_=0; i_<=nvars-1;i_++)
+                    {
+                        ct[cidx+1,i_] = initbuf.rm0[ptidx,i_];
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Internal error
+            //
+            alglib.ap.assert(false, "SelectInitialCenters: internal error");
+        }
+
+
+        /*************************************************************************
+        This function "fixes" centers, i.e. replaces ones which have  no  neighbor
+        points by new centers which have at least one neighbor. If it is impossible
+        to fix centers (not enough distinct points in the dataset), this function
+        returns False.
+
+        INPUT PARAMETERS:
+            XY          -   dataset, array [0..NPoints-1,0..NVars-1].
+            NPoints     -   points count, >=1
+            NVars       -   number of variables, NVars>=1
+            CT          -   centers
+            K           -   number of centers, K>=1
+            InitBuf     -   internal buffer, possibly unitialized instance of
+                            APBuffers. It is recommended to use this instance only
+                            with SelectInitialCenters() and FixCenters() functions,
+                            because these functions may allocate really large storage.
+            UpdatePool  -   shared pool seeded with instance of APBuffers structure
+                            (seed instance can be unitialized). Used internally with
+                            KMeansUpdateDistances() function. It is recommended
+                            to use this pool ONLY with KMeansUpdateDistances()
+                            function.
+
+        OUTPUT PARAMETERS:
+            CT          -   set of K centers, one per row
+            
+        RESULT:
+            True on success, False on failure (impossible to create K independent clusters)
+
+          -- ALGLIB --
+             Copyright 21.01.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool fixcenters(double[,] xy,
+            int npoints,
+            int nvars,
+            double[,] ct,
+            int k,
+            apserv.apbuffers initbuf,
+            alglib.smp.shared_pool updatepool,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int fixiteration = 0;
+            int centertofix = 0;
+            int i = 0;
+            int j = 0;
+            int pdistant = 0;
+            double ddistant = 0;
+            double v = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(npoints>=1, "FixCenters: internal error");
+            alglib.ap.assert(nvars>=1, "FixCenters: internal error");
+            alglib.ap.assert(k>=1, "FixCenters: internal error");
+            
+            //
+            // Calculate distances from points to best centers (RA0)
+            // and best center indexes (IA0)
+            //
+            apserv.ivectorsetlengthatleast(ref initbuf.ia0, npoints, _params);
+            apserv.rvectorsetlengthatleast(ref initbuf.ra0, npoints, _params);
+            kmeansupdatedistances(xy, 0, npoints, nvars, ct, 0, k, initbuf.ia0, initbuf.ra0, updatepool, _params);
+            
+            //
+            // Repeat loop:
+            // * find first center which has no corresponding point
+            // * set it to the most distant (from the rest of the centerset) point
+            // * recalculate distances, update IA0/RA0
+            // * repeat
+            //
+            // Loop is repeated for at most 2*K iterations. It is stopped once we have
+            // no "empty" clusters.
+            //
+            apserv.bvectorsetlengthatleast(ref initbuf.ba0, k, _params);
+            for(fixiteration=0; fixiteration<=2*k; fixiteration++)
+            {
+                
+                //
+                // Select center to fix (one which is not mentioned in IA0),
+                // terminate if there is no such center.
+                // BA0[] stores True for centers which have at least one point.
+                //
+                for(i=0; i<=k-1; i++)
+                {
+                    initbuf.ba0[i] = false;
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    initbuf.ba0[initbuf.ia0[i]] = true;
+                }
+                centertofix = -1;
+                for(i=0; i<=k-1; i++)
+                {
+                    if( !initbuf.ba0[i] )
+                    {
+                        centertofix = i;
+                        break;
+                    }
+                }
+                if( centertofix<0 )
+                {
+                    result = true;
+                    return result;
+                }
+                
+                //
+                // Replace center to fix by the most distant point.
+                // Update IA0/RA0
+                //
+                pdistant = 0;
+                ddistant = initbuf.ra0[pdistant];
+                for(i=0; i<=npoints-1; i++)
+                {
+                    if( (double)(initbuf.ra0[i])>(double)(ddistant) )
+                    {
+                        ddistant = initbuf.ra0[i];
+                        pdistant = i;
+                    }
+                }
+                if( (double)(ddistant)==(double)(0.0) )
+                {
+                    break;
+                }
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    ct[centertofix,i_] = xy[pdistant,i_];
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    v = 0.0;
+                    for(j=0; j<=nvars-1; j++)
+                    {
+                        v = v+math.sqr(xy[i,j]-ct[centertofix,j]);
+                    }
+                    if( (double)(v)<(double)(initbuf.ra0[i]) )
+                    {
+                        initbuf.ra0[i] = v;
+                        initbuf.ia0[i] = centertofix;
+                    }
+                }
+            }
+            result = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This  function  performs  agglomerative  hierarchical  clustering    using
+        precomputed  distance  matrix.  Internal  function,  should  not be called
+        directly.
+
+        INPUT PARAMETERS:
+            S       -   clusterizer state, initialized by ClusterizerCreate()
+            D       -   distance matrix, array[S.NFeatures,S.NFeatures]
+                        Contents of the matrix is destroyed during
+                        algorithm operation.
+
+        OUTPUT PARAMETERS:
+            Rep     -   clustering results; see description of AHCReport
+                        structure for more information.
+
+          -- ALGLIB --
+             Copyright 10.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        private static void clusterizerrunahcinternal(clusterizerstate s,
+            ref double[,] d,
+            ahcreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            int mergeidx = 0;
+            int c0 = 0;
+            int c1 = 0;
+            int s0 = 0;
+            int s1 = 0;
+            int ar = 0;
+            int br = 0;
+            int npoints = 0;
+            int[] cidx = new int[0];
+            int[] csizes = new int[0];
+            int[] nnidx = new int[0];
+            int[,] cinfo = new int[0,0];
+            int n0 = 0;
+            int n1 = 0;
+            int ni = 0;
+            double d01 = 0;
+
+            npoints = s.npoints;
+            
+            //
+            // Fill Rep.NPoints, quick exit when NPoints<=1
+            //
+            rep.npoints = npoints;
+            if( npoints==0 )
+            {
+                rep.p = new int[0];
+                rep.z = new int[0, 0];
+                rep.pz = new int[0, 0];
+                rep.pm = new int[0, 0];
+                rep.mergedist = new double[0];
+                rep.terminationtype = 1;
+                return;
+            }
+            if( npoints==1 )
+            {
+                rep.p = new int[1];
+                rep.z = new int[0, 0];
+                rep.pz = new int[0, 0];
+                rep.pm = new int[0, 0];
+                rep.mergedist = new double[0];
+                rep.p[0] = 0;
+                rep.terminationtype = 1;
+                return;
+            }
+            rep.z = new int[npoints-1, 2];
+            rep.mergedist = new double[npoints-1];
+            rep.terminationtype = 1;
+            
+            //
+            // Build list of nearest neighbors
+            //
+            nnidx = new int[npoints];
+            for(i=0; i<=npoints-1; i++)
+            {
+                
+                //
+                // Calculate index of the nearest neighbor
+                //
+                k = -1;
+                v = math.maxrealnumber;
+                for(j=0; j<=npoints-1; j++)
+                {
+                    if( j!=i && (double)(d[i,j])<(double)(v) )
+                    {
+                        k = j;
+                        v = d[i,j];
+                    }
+                }
+                alglib.ap.assert((double)(v)<(double)(math.maxrealnumber), "ClusterizerRunAHC: internal error");
+                nnidx[i] = k;
+            }
+            
+            //
+            // For AHCAlgo=4 (Ward's method) replace distances by their squares times 0.5
+            //
+            if( s.ahcalgo==4 )
+            {
+                for(i=0; i<=npoints-1; i++)
+                {
+                    for(j=0; j<=npoints-1; j++)
+                    {
+                        d[i,j] = 0.5*d[i,j]*d[i,j];
+                    }
+                }
+            }
+            
+            //
+            // Distance matrix is built, perform merges.
+            //
+            // NOTE 1: CIdx is array[NPoints] which maps rows/columns of the
+            //         distance matrix D to indexes of clusters. Values of CIdx
+            //         from [0,NPoints) denote single-point clusters, and values
+            //         from [NPoints,2*NPoints-1) denote ones obtained by merging
+            //         smaller clusters. Negative calues correspond to absent clusters.
+            //
+            //         Initially it contains [0...NPoints-1], after each merge
+            //         one element of CIdx (one with index C0) is replaced by
+            //         NPoints+MergeIdx, and another one with index C1 is
+            //         rewritten by -1.
+            // 
+            // NOTE 2: CSizes is array[NPoints] which stores sizes of clusters.
+            //         
+            //
+            cidx = new int[npoints];
+            csizes = new int[npoints];
+            for(i=0; i<=npoints-1; i++)
+            {
+                cidx[i] = i;
+                csizes[i] = 1;
+            }
+            for(mergeidx=0; mergeidx<=npoints-2; mergeidx++)
+            {
+                
+                //
+                // Select pair of clusters (C0,C1) with CIdx[C0]<CIdx[C1] to merge.
+                //
+                c0 = -1;
+                c1 = -1;
+                d01 = math.maxrealnumber;
+                for(i=0; i<=npoints-1; i++)
+                {
+                    if( cidx[i]>=0 )
+                    {
+                        if( (double)(d[i,nnidx[i]])<(double)(d01) )
+                        {
+                            c0 = i;
+                            c1 = nnidx[i];
+                            d01 = d[i,nnidx[i]];
+                        }
+                    }
+                }
+                alglib.ap.assert((double)(d01)<(double)(math.maxrealnumber), "ClusterizerRunAHC: internal error");
+                if( cidx[c0]>cidx[c1] )
+                {
+                    i = c1;
+                    c1 = c0;
+                    c0 = i;
+                }
+                
+                //
+                // Fill one row of Rep.Z and one element of Rep.MergeDist
+                //
+                rep.z[mergeidx,0] = cidx[c0];
+                rep.z[mergeidx,1] = cidx[c1];
+                rep.mergedist[mergeidx] = d01;
+                
+                //
+                // Update distance matrix:
+                // * row/column C0 are updated by distances to the new cluster
+                // * row/column C1 are considered empty (we can fill them by zeros,
+                //   but do not want to spend time - we just ignore them)
+                //
+                // NOTE: it is important to update distance matrix BEFORE CIdx/CSizes
+                //       are updated.
+                //
+                alglib.ap.assert((((s.ahcalgo==0 || s.ahcalgo==1) || s.ahcalgo==2) || s.ahcalgo==3) || s.ahcalgo==4, "ClusterizerRunAHC: internal error");
+                for(i=0; i<=npoints-1; i++)
+                {
+                    if( i!=c0 && i!=c1 )
+                    {
+                        n0 = csizes[c0];
+                        n1 = csizes[c1];
+                        ni = csizes[i];
+                        if( s.ahcalgo==0 )
+                        {
+                            d[i,c0] = Math.Max(d[i,c0], d[i,c1]);
+                        }
+                        if( s.ahcalgo==1 )
+                        {
+                            d[i,c0] = Math.Min(d[i,c0], d[i,c1]);
+                        }
+                        if( s.ahcalgo==2 )
+                        {
+                            d[i,c0] = (csizes[c0]*d[i,c0]+csizes[c1]*d[i,c1])/(csizes[c0]+csizes[c1]);
+                        }
+                        if( s.ahcalgo==3 )
+                        {
+                            d[i,c0] = (d[i,c0]+d[i,c1])/2;
+                        }
+                        if( s.ahcalgo==4 )
+                        {
+                            d[i,c0] = ((n0+ni)*d[i,c0]+(n1+ni)*d[i,c1]-ni*d01)/(n0+n1+ni);
+                        }
+                        d[c0,i] = d[i,c0];
+                    }
+                }
+                
+                //
+                // Update CIdx and CSizes
+                //
+                cidx[c0] = npoints+mergeidx;
+                cidx[c1] = -1;
+                csizes[c0] = csizes[c0]+csizes[c1];
+                csizes[c1] = 0;
+                
+                //
+                // Update nearest neighbors array:
+                // * update nearest neighbors of everything except for C0/C1
+                // * update neighbors of C0/C1
+                //
+                for(i=0; i<=npoints-1; i++)
+                {
+                    if( (cidx[i]>=0 && i!=c0) && (nnidx[i]==c0 || nnidx[i]==c1) )
+                    {
+                        
+                        //
+                        // I-th cluster which is distinct from C0/C1 has former C0/C1 cluster as its nearest
+                        // neighbor. We handle this issue depending on specific AHC algorithm being used.
+                        //
+                        if( s.ahcalgo==1 )
+                        {
+                            
+                            //
+                            // Single linkage. Merging of two clusters together
+                            // does NOT change distances between new cluster and
+                            // other clusters.
+                            //
+                            // The only thing we have to do is to update nearest neighbor index
+                            //
+                            nnidx[i] = c0;
+                        }
+                        else
+                        {
+                            
+                            //
+                            // Something other than single linkage. We have to re-examine
+                            // all the row to find nearest neighbor.
+                            //
+                            k = -1;
+                            v = math.maxrealnumber;
+                            for(j=0; j<=npoints-1; j++)
+                            {
+                                if( (cidx[j]>=0 && j!=i) && (double)(d[i,j])<(double)(v) )
+                                {
+                                    k = j;
+                                    v = d[i,j];
+                                }
+                            }
+                            alglib.ap.assert((double)(v)<(double)(math.maxrealnumber) || mergeidx==npoints-2, "ClusterizerRunAHC: internal error");
+                            nnidx[i] = k;
+                        }
+                    }
+                }
+                k = -1;
+                v = math.maxrealnumber;
+                for(j=0; j<=npoints-1; j++)
+                {
+                    if( (cidx[j]>=0 && j!=c0) && (double)(d[c0,j])<(double)(v) )
+                    {
+                        k = j;
+                        v = d[c0,j];
+                    }
+                }
+                alglib.ap.assert((double)(v)<(double)(math.maxrealnumber) || mergeidx==npoints-2, "ClusterizerRunAHC: internal error");
+                nnidx[c0] = k;
+            }
+            
+            //
+            // Calculate Rep.P and Rep.PM.
+            //
+            // In order to do that, we fill CInfo matrix - (2*NPoints-1)*3 matrix,
+            // with I-th row containing:
+            // * CInfo[I,0]     -   size of I-th cluster
+            // * CInfo[I,1]     -   beginning of I-th cluster
+            // * CInfo[I,2]     -   end of I-th cluster
+            // * CInfo[I,3]     -   height of I-th cluster
+            //
+            // We perform it as follows:
+            // * first NPoints clusters have unit size (CInfo[I,0]=1) and zero
+            //   height (CInfo[I,3]=0)
+            // * we replay NPoints-1 merges from first to last and fill sizes of
+            //   corresponding clusters (new size is a sum of sizes of clusters
+            //   being merged) and height (new height is max(heights)+1).
+            // * now we ready to determine locations of clusters. Last cluster
+            //   spans entire dataset, we know it. We replay merges from last to
+            //   first, during each merge we already know location of the merge
+            //   result, and we can position first cluster to the left part of
+            //   the result, and second cluster to the right part.
+            //
+            rep.p = new int[npoints];
+            rep.pm = new int[npoints-1, 6];
+            cinfo = new int[2*npoints-1, 4];
+            for(i=0; i<=npoints-1; i++)
+            {
+                cinfo[i,0] = 1;
+                cinfo[i,3] = 0;
+            }
+            for(i=0; i<=npoints-2; i++)
+            {
+                cinfo[npoints+i,0] = cinfo[rep.z[i,0],0]+cinfo[rep.z[i,1],0];
+                cinfo[npoints+i,3] = Math.Max(cinfo[rep.z[i,0],3], cinfo[rep.z[i,1],3])+1;
+            }
+            cinfo[2*npoints-2,1] = 0;
+            cinfo[2*npoints-2,2] = npoints-1;
+            for(i=npoints-2; i>=0; i--)
+            {
+                
+                //
+                // We merge C0 which spans [A0,B0] and C1 (spans [A1,B1]),
+                // with unknown A0, B0, A1, B1. However, we know that result
+                // is CR, which spans [AR,BR] with known AR/BR, and we know
+                // sizes of C0, C1, CR (denotes as S0, S1, SR).
+                //
+                c0 = rep.z[i,0];
+                c1 = rep.z[i,1];
+                s0 = cinfo[c0,0];
+                s1 = cinfo[c1,0];
+                ar = cinfo[npoints+i,1];
+                br = cinfo[npoints+i,2];
+                cinfo[c0,1] = ar;
+                cinfo[c0,2] = ar+s0-1;
+                cinfo[c1,1] = br-(s1-1);
+                cinfo[c1,2] = br;
+                rep.pm[i,0] = cinfo[c0,1];
+                rep.pm[i,1] = cinfo[c0,2];
+                rep.pm[i,2] = cinfo[c1,1];
+                rep.pm[i,3] = cinfo[c1,2];
+                rep.pm[i,4] = cinfo[c0,3];
+                rep.pm[i,5] = cinfo[c1,3];
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                alglib.ap.assert(cinfo[i,1]==cinfo[i,2]);
+                rep.p[i] = cinfo[i,1];
+            }
+            
+            //
+            // Calculate Rep.PZ
+            //
+            rep.pz = new int[npoints-1, 2];
+            for(i=0; i<=npoints-2; i++)
+            {
+                rep.pz[i,0] = rep.z[i,0];
+                rep.pz[i,1] = rep.z[i,1];
+                if( rep.pz[i,0]<npoints )
+                {
+                    rep.pz[i,0] = rep.p[rep.pz[i,0]];
+                }
+                if( rep.pz[i,1]<npoints )
+                {
+                    rep.pz[i,1] = rep.p[rep.pz[i,1]];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function recursively evaluates distance matrix  for  SOME  (not all!)
+        distance types.
+
+        INPUT PARAMETERS:
+            XY      -   array[?,NFeatures], dataset
+            NFeatures-  number of features, >=1
+            DistType-   distance function:
+                        *  0    Chebyshev distance  (L-inf norm)
+                        *  1    city block distance (L1 norm)
+            D       -   preallocated output matrix
+            I0,I1   -   half interval of rows to calculate: [I0,I1) is processed
+            J0,J1   -   half interval of cols to calculate: [J0,J1) is processed
+
+        OUTPUT PARAMETERS:
+            D       -   array[NPoints,NPoints], distance matrix
+                        upper triangle and main diagonal are initialized with
+                        data.
+
+        NOTE: intersection of [I0,I1) and [J0,J1)  may  completely  lie  in  upper
+              triangle, only partially intersect with it, or have zero intersection.
+              In any case, only intersection of submatrix given by [I0,I1)*[J0,J1)
+              with upper triangle of the matrix is evaluated.
+              
+              Say, for 4x4 distance matrix A:
+              * [0,2)*[0,2) will result in evaluation of A00, A01, A11
+              * [2,4)*[2,4) will result in evaluation of A22, A23, A32, A33
+              * [2,4)*[0,2) will result in evaluation of empty set of elements
+              
+
+          -- ALGLIB --
+             Copyright 07.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void evaluatedistancematrixrec(double[,] xy,
+            int nfeatures,
+            int disttype,
+            double[,] d,
+            int i0,
+            int i1,
+            int j0,
+            int j1,
+            alglib.xparams _params)
+        {
+            double rcomplexity = 0;
+            int len0 = 0;
+            int len1 = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            double vv = 0;
+
+            alglib.ap.assert(disttype==0 || disttype==1, "EvaluateDistanceMatrixRec: incorrect DistType");
+            
+            //
+            // Normalize J0/J1:
+            // * J0:=max(J0,I0) - we ignore lower triangle
+            // * J1:=max(J1,J0) - normalize J1
+            //
+            j0 = Math.Max(j0, i0);
+            j1 = Math.Max(j1, j0);
+            if( j1<=j0 || i1<=i0 )
+            {
+                return;
+            }
+            rcomplexity = complexitymultiplier*apserv.rmul3(i1-i0, j1-j0, nfeatures, _params);
+            if( (i1-i0>2 || j1-j0>2) && (double)(rcomplexity)>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_evaluatedistancematrixrec(xy,nfeatures,disttype,d,i0,i1,j0,j1, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Try to process in parallel. Two condtions must hold in order to
+            // activate parallel processing:
+            // 1. I1-I0>2 or J1-J0>2
+            // 2. (I1-I0)*(J1-J0)*NFeatures>=ParallelComplexity
+            //
+            // NOTE: all quantities are converted to reals in order to avoid
+            //       integer overflow during multiplication
+            //
+            // NOTE: strict inequality in (1) is necessary to reduce task to 2x2
+            //       basecases. In future versions we will be able to handle such
+            //       basecases more efficiently than 1x1 cases.
+            //
+            if( (double)(rcomplexity)>=(double)(apserv.spawnlevel(_params)) && (i1-i0>2 || j1-j0>2) )
+            {
+                
+                //
+                // Recursive division along largest of dimensions
+                //
+                if( i1-i0>j1-j0 )
+                {
+                    apserv.splitlengtheven(i1-i0, ref len0, ref len1, _params);
+                    evaluatedistancematrixrec(xy, nfeatures, disttype, d, i0, i0+len0, j0, j1, _params);
+                    evaluatedistancematrixrec(xy, nfeatures, disttype, d, i0+len0, i1, j0, j1, _params);
+                }
+                else
+                {
+                    apserv.splitlengtheven(j1-j0, ref len0, ref len1, _params);
+                    evaluatedistancematrixrec(xy, nfeatures, disttype, d, i0, i1, j0, j0+len0, _params);
+                    evaluatedistancematrixrec(xy, nfeatures, disttype, d, i0, i1, j0+len0, j1, _params);
+                }
+                return;
+            }
+            
+            //
+            // Sequential processing
+            //
+            for(i=i0; i<=i1-1; i++)
+            {
+                for(j=j0; j<=j1-1; j++)
+                {
+                    if( j>=i )
+                    {
+                        v = 0.0;
+                        if( disttype==0 )
+                        {
+                            for(k=0; k<=nfeatures-1; k++)
+                            {
+                                vv = xy[i,k]-xy[j,k];
+                                if( (double)(vv)<(double)(0) )
+                                {
+                                    vv = -vv;
+                                }
+                                if( (double)(vv)>(double)(v) )
+                                {
+                                    v = vv;
+                                }
+                            }
+                        }
+                        if( disttype==1 )
+                        {
+                            for(k=0; k<=nfeatures-1; k++)
+                            {
+                                vv = xy[i,k]-xy[j,k];
+                                if( (double)(vv)<(double)(0) )
+                                {
+                                    vv = -vv;
+                                }
+                                v = v+vv;
+                            }
+                        }
+                        d[i,j] = v;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_evaluatedistancematrixrec(double[,] xy,
+            int nfeatures,
+            int disttype,
+            double[,] d,
+            int i0,
+            int i1,
+            int j0,
+            int j1, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+    }
+    public class dforest
+    {
+        /*************************************************************************
+        A random forest (decision forest) builder object.
+
+        Used to store dataset and specify random forest training algorithm settings.
+        *************************************************************************/
+        public class decisionforestbuilder : apobject
+        {
+            public int dstype;
+            public int npoints;
+            public int nvars;
+            public int nclasses;
+            public double[] dsdata;
+            public double[] dsrval;
+            public int[] dsival;
+            public int rdfalgo;
+            public double rdfratio;
+            public double rdfvars;
+            public int rdfglobalseed;
+            public int rdfsplitstrength;
+            public double[] dsmin;
+            public double[] dsmax;
+            public bool[] dsbinary;
+            public double dsravg;
+            public int[] dsctotals;
+            public int rdfprogress;
+            public int rdftotal;
+            public alglib.smp.shared_pool workpool;
+            public alglib.smp.shared_pool votepool;
+            public alglib.smp.shared_pool treepool;
+            public alglib.smp.shared_pool treefactory;
+            public decisionforestbuilder()
+            {
+                init();
+            }
+            public override void init()
+            {
+                dsdata = new double[0];
+                dsrval = new double[0];
+                dsival = new int[0];
+                dsmin = new double[0];
+                dsmax = new double[0];
+                dsbinary = new bool[0];
+                dsctotals = new int[0];
+                workpool = new alglib.smp.shared_pool();
+                votepool = new alglib.smp.shared_pool();
+                treepool = new alglib.smp.shared_pool();
+                treefactory = new alglib.smp.shared_pool();
+            }
+            public override alglib.apobject make_copy()
+            {
+                decisionforestbuilder _result = new decisionforestbuilder();
+                _result.dstype = dstype;
+                _result.npoints = npoints;
+                _result.nvars = nvars;
+                _result.nclasses = nclasses;
+                _result.dsdata = (double[])dsdata.Clone();
+                _result.dsrval = (double[])dsrval.Clone();
+                _result.dsival = (int[])dsival.Clone();
+                _result.rdfalgo = rdfalgo;
+                _result.rdfratio = rdfratio;
+                _result.rdfvars = rdfvars;
+                _result.rdfglobalseed = rdfglobalseed;
+                _result.rdfsplitstrength = rdfsplitstrength;
+                _result.dsmin = (double[])dsmin.Clone();
+                _result.dsmax = (double[])dsmax.Clone();
+                _result.dsbinary = (bool[])dsbinary.Clone();
+                _result.dsravg = dsravg;
+                _result.dsctotals = (int[])dsctotals.Clone();
+                _result.rdfprogress = rdfprogress;
+                _result.rdftotal = rdftotal;
+                _result.workpool = (alglib.smp.shared_pool)workpool.make_copy();
+                _result.votepool = (alglib.smp.shared_pool)votepool.make_copy();
+                _result.treepool = (alglib.smp.shared_pool)treepool.make_copy();
+                _result.treefactory = (alglib.smp.shared_pool)treefactory.make_copy();
+                return _result;
+            }
+        };
+
+
+        public class dfworkbuf : apobject
+        {
+            public int[] classpriors;
+            public int[] varpool;
+            public int varpoolsize;
+            public int[] trnset;
+            public int trnsize;
+            public double[] trnlabelsr;
+            public int[] trnlabelsi;
+            public int[] oobset;
+            public int oobsize;
+            public double[] treebuf;
+            public double[] curvals;
+            public double[] bestvals;
+            public int[] tmp0i;
+            public int[] tmp1i;
+            public double[] tmp0r;
+            public double[] tmp1r;
+            public double[] tmp2r;
+            public double[] tmp3r;
+            public int[] classtotals0;
+            public int[] classtotals1;
+            public int[] classtotals01;
+            public dfworkbuf()
+            {
+                init();
+            }
+            public override void init()
+            {
+                classpriors = new int[0];
+                varpool = new int[0];
+                trnset = new int[0];
+                trnlabelsr = new double[0];
+                trnlabelsi = new int[0];
+                oobset = new int[0];
+                treebuf = new double[0];
+                curvals = new double[0];
+                bestvals = new double[0];
+                tmp0i = new int[0];
+                tmp1i = new int[0];
+                tmp0r = new double[0];
+                tmp1r = new double[0];
+                tmp2r = new double[0];
+                tmp3r = new double[0];
+                classtotals0 = new int[0];
+                classtotals1 = new int[0];
+                classtotals01 = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                dfworkbuf _result = new dfworkbuf();
+                _result.classpriors = (int[])classpriors.Clone();
+                _result.varpool = (int[])varpool.Clone();
+                _result.varpoolsize = varpoolsize;
+                _result.trnset = (int[])trnset.Clone();
+                _result.trnsize = trnsize;
+                _result.trnlabelsr = (double[])trnlabelsr.Clone();
+                _result.trnlabelsi = (int[])trnlabelsi.Clone();
+                _result.oobset = (int[])oobset.Clone();
+                _result.oobsize = oobsize;
+                _result.treebuf = (double[])treebuf.Clone();
+                _result.curvals = (double[])curvals.Clone();
+                _result.bestvals = (double[])bestvals.Clone();
+                _result.tmp0i = (int[])tmp0i.Clone();
+                _result.tmp1i = (int[])tmp1i.Clone();
+                _result.tmp0r = (double[])tmp0r.Clone();
+                _result.tmp1r = (double[])tmp1r.Clone();
+                _result.tmp2r = (double[])tmp2r.Clone();
+                _result.tmp3r = (double[])tmp3r.Clone();
+                _result.classtotals0 = (int[])classtotals0.Clone();
+                _result.classtotals1 = (int[])classtotals1.Clone();
+                _result.classtotals01 = (int[])classtotals01.Clone();
+                return _result;
+            }
+        };
+
+
+        public class dfvotebuf : apobject
+        {
+            public double[] trntotals;
+            public double[] oobtotals;
+            public int[] trncounts;
+            public int[] oobcounts;
+            public dfvotebuf()
+            {
+                init();
+            }
+            public override void init()
+            {
+                trntotals = new double[0];
+                oobtotals = new double[0];
+                trncounts = new int[0];
+                oobcounts = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                dfvotebuf _result = new dfvotebuf();
+                _result.trntotals = (double[])trntotals.Clone();
+                _result.oobtotals = (double[])oobtotals.Clone();
+                _result.trncounts = (int[])trncounts.Clone();
+                _result.oobcounts = (int[])oobcounts.Clone();
+                return _result;
+            }
+        };
+
+
+        public class dftreebuf : apobject
+        {
+            public double[] treebuf;
+            public dftreebuf()
+            {
+                init();
+            }
+            public override void init()
+            {
+                treebuf = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                dftreebuf _result = new dftreebuf();
+                _result.treebuf = (double[])treebuf.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Buffer object which is used to perform  various  requests  (usually  model
+        inference) in the multithreaded mode (multiple threads working  with  same
+        DF object).
+
+        This object should be created with DFCreateBuffer().
+        *************************************************************************/
+        public class decisionforestbuffer : apobject
+        {
+            public double[] x;
+            public double[] y;
+            public decisionforestbuffer()
+            {
+                init();
+            }
+            public override void init()
+            {
+                x = new double[0];
+                y = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                decisionforestbuffer _result = new decisionforestbuffer();
+                _result.x = (double[])x.Clone();
+                _result.y = (double[])y.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Decision forest (random forest) model.
+        *************************************************************************/
+        public class decisionforest : apobject
+        {
+            public int nvars;
+            public int nclasses;
+            public int ntrees;
+            public int bufsize;
+            public double[] trees;
+            public decisionforestbuffer buffer;
+            public decisionforest()
+            {
+                init();
+            }
+            public override void init()
+            {
+                trees = new double[0];
+                buffer = new decisionforestbuffer();
+            }
+            public override alglib.apobject make_copy()
+            {
+                decisionforest _result = new decisionforest();
+                _result.nvars = nvars;
+                _result.nclasses = nclasses;
+                _result.ntrees = ntrees;
+                _result.bufsize = bufsize;
+                _result.trees = (double[])trees.Clone();
+                _result.buffer = (decisionforestbuffer)buffer.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Decision forest training report.
+
+        Following fields store training set errors:
+        * relclserror       -   fraction of misclassified cases, [0,1]
+        * avgce             -   average cross-entropy in bits per symbol
+        * rmserror          -   root-mean-square error
+        * avgerror          -   average error
+        * avgrelerror       -   average relative error
+
+        Out-of-bag estimates are stored in fields with same names, but "oob" prefix.
+
+        For classification problems:
+        * RMS, AVG and AVGREL errors are calculated for posterior probabilities
+
+        For regression problems:
+        * RELCLS and AVGCE errors are zero
+        *************************************************************************/
+        public class dfreport : apobject
+        {
+            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 dfreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                dfreport _result = new dfreport();
+                _result.relclserror = relclserror;
+                _result.avgce = avgce;
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.avgrelerror = avgrelerror;
+                _result.oobrelclserror = oobrelclserror;
+                _result.oobavgce = oobavgce;
+                _result.oobrmserror = oobrmserror;
+                _result.oobavgerror = oobavgerror;
+                _result.oobavgrelerror = oobavgrelerror;
+                return _result;
+            }
+        };
+
+
+        public class dfinternalbuffers : apobject
+        {
+            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()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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 override alglib.apobject make_copy()
+            {
+                dfinternalbuffers _result = new dfinternalbuffers();
+                _result.treebuf = (double[])treebuf.Clone();
+                _result.idxbuf = (int[])idxbuf.Clone();
+                _result.tmpbufr = (double[])tmpbufr.Clone();
+                _result.tmpbufr2 = (double[])tmpbufr2.Clone();
+                _result.tmpbufi = (int[])tmpbufi.Clone();
+                _result.classibuf = (int[])classibuf.Clone();
+                _result.sortrbuf = (double[])sortrbuf.Clone();
+                _result.sortrbuf2 = (double[])sortrbuf2.Clone();
+                _result.sortibuf = (int[])sortibuf.Clone();
+                _result.varpool = (int[])varpool.Clone();
+                _result.evsbin = (bool[])evsbin.Clone();
+                _result.evssplits = (double[])evssplits.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        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 function creates buffer  structure  which  can  be  used  to  perform
+        parallel inference requests.
+
+        DF subpackage  provides two sets of computing functions - ones  which  use
+        internal buffer of DF model  (these  functions are single-threaded because
+        they use same buffer, which can not  shared  between  threads),  and  ones
+        which use external buffer.
+
+        This function is used to initialize external buffer.
+
+        INPUT PARAMETERS
+            Model       -   DF model which is associated with newly created buffer
+
+        OUTPUT PARAMETERS
+            Buf         -   external buffer.
+            
+            
+        IMPORTANT: buffer object should be used only with model which was used  to
+                   initialize buffer. Any attempt to  use  buffer  with  different
+                   object is dangerous - you  may   get  integrity  check  failure
+                   (exception) because sizes of internal  arrays  do  not  fit  to
+                   dimensions of the model structure.
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfcreatebuffer(decisionforest model,
+            decisionforestbuffer buf,
+            alglib.xparams _params)
+        {
+            buf.x = new double[model.nvars];
+            buf.y = new double[model.nclasses];
+        }
+
+
+        /*************************************************************************
+        This subroutine creates DecisionForestBuilder  object  which  is  used  to
+        train random forests.
+
+        By default, new builder stores empty dataset and some  reasonable  default
+        settings. At the very least, you should specify dataset prior to  building
+        decision forest. You can also tweak settings of  the  forest  construction
+        algorithm (recommended, although default setting should work well).
+
+        Following actions are mandatory:
+        * calling dfbuildersetdataset() to specify dataset
+        * calling dfbuilderbuildrandomforest() to build random forest using current
+          dataset and default settings
+          
+        Additionally, you may call:
+        * dfbuildersetrndvars() or dfbuildersetrndvarsratio() to specify number of
+          variables randomly chosen for each split
+        * dfbuildersetsubsampleratio() to specify fraction of the dataset randomly
+          subsampled to build each tree
+        * dfbuildersetseed() to control random seed chosen for tree construction
+
+        INPUT PARAMETERS:
+            none
+
+        OUTPUT PARAMETERS:
+            S           -   decision forest builder
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfbuildercreate(decisionforestbuilder s,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Empty dataset
+            //
+            s.dstype = -1;
+            s.npoints = 0;
+            s.nvars = 0;
+            s.nclasses = 1;
+            
+            //
+            // Default training settings
+            //
+            s.rdfalgo = 0;
+            s.rdfratio = 0.5;
+            s.rdfvars = 0.0;
+            s.rdfglobalseed = 0;
+            s.rdfsplitstrength = 2;
+            
+            //
+            // Other fields
+            //
+            s.rdfprogress = 0;
+            s.rdftotal = 1;
+        }
+
+
+        /*************************************************************************
+        This subroutine adds dense dataset to the internal storage of the  builder
+        object. Specifying your dataset in the dense format means that  the  dense
+        version of the forest construction algorithm will be invoked.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object
+            XY          -   array[NPoints,NVars+1] (minimum size; actual size  can
+                            be larger, only leading part is used anyway), dataset:
+                            * first NVars elements of each row store values of the
+                              independent variables
+                            * last  column  store class number (in 0...NClasses-1)
+                              or real value of the dependent variable
+            NPoints     -   number of rows in the dataset, NPoints>=1
+            NVars       -   number of independent variables, NVars>=1 
+            NClasses    -   indicates type of the problem being solved:
+                            * NClasses>=2 means  that  classification  problem  is
+                              solved  (last  column  of  the  dataset stores class
+                              number)
+                            * NClasses=1 means that regression problem  is  solved
+                              (last column of the dataset stores variable value)
+
+        OUTPUT PARAMETERS:
+            S           -   decision forest builder
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfbuildersetdataset(decisionforestbuilder s,
+            double[,] xy,
+            int npoints,
+            int nvars,
+            int nclasses,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            
+            //
+            // Check parameters
+            //
+            alglib.ap.assert(npoints>=1, "dfbuildersetdataset: npoints<1");
+            alglib.ap.assert(nvars>=1, "dfbuildersetdataset: nvars<1");
+            alglib.ap.assert(nclasses>=1, "dfbuildersetdataset: nclasses<1");
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "dfbuildersetdataset: rows(xy)<npoints");
+            alglib.ap.assert(alglib.ap.cols(xy)>=nvars+1, "dfbuildersetdataset: cols(xy)<nvars+1");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, npoints, nvars+1, _params), "dfbuildersetdataset: xy parameter contains INFs or NANs");
+            if( nclasses>1 )
+            {
+                for(i=0; i<=npoints-1; i++)
+                {
+                    j = (int)Math.Round(xy[i,nvars]);
+                    alglib.ap.assert(j>=0 && j<nclasses, "dfbuildersetdataset: last column of xy contains invalid class number");
+                }
+            }
+            
+            //
+            // Set dataset
+            //
+            s.dstype = 0;
+            s.npoints = npoints;
+            s.nvars = nvars;
+            s.nclasses = nclasses;
+            apserv.rvectorsetlengthatleast(ref s.dsdata, npoints*nvars, _params);
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=nvars-1; j++)
+                {
+                    s.dsdata[j*npoints+i] = xy[i,j];
+                }
+            }
+            if( nclasses>1 )
+            {
+                apserv.ivectorsetlengthatleast(ref s.dsival, npoints, _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    s.dsival[i] = (int)Math.Round(xy[i,nvars]);
+                }
+            }
+            else
+            {
+                apserv.rvectorsetlengthatleast(ref s.dsrval, npoints, _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    s.dsrval[i] = xy[i,nvars];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets number of variables (in [1,NVars] range) used by random
+        forest construction algorithm.
+
+        The default option is to use roughly sqrt(NVars) variables.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object
+            RndVars     -   number of randomly selected variables; values  outside
+                            of [1,NVars] range are silently clipped.
+
+        OUTPUT PARAMETERS:
+            S           -   decision forest builder
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfbuildersetrndvars(decisionforestbuilder s,
+            int rndvars,
+            alglib.xparams _params)
+        {
+            s.rdfvars = Math.Max(rndvars, 1);
+        }
+
+
+        /*************************************************************************
+        This function sets number of variables used by random forest  construction
+        algorithm as a fraction of total variable count (0,1) range.
+
+        The default option is to use roughly sqrt(NVars) variables.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object
+            F           -   round(NVars*F) variables are selected
+
+        OUTPUT PARAMETERS:
+            S           -   decision forest builder
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfbuildersetrndvarsratio(decisionforestbuilder s,
+            double f,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(f), "dfbuildersetrndvarsratio: F is INF or NAN");
+            s.rdfvars = -Math.Max(f, math.machineepsilon);
+        }
+
+
+        /*************************************************************************
+        This function tells random forest builder to automatically  choose  number
+        of  variables  used  by  random  forest  construction  algorithm.  Roughly
+        sqrt(NVars) variables will be used.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object
+
+        OUTPUT PARAMETERS:
+            S           -   decision forest builder
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfbuildersetrndvarsauto(decisionforestbuilder s,
+            alglib.xparams _params)
+        {
+            s.rdfvars = 0;
+        }
+
+
+        /*************************************************************************
+        This function sets size of dataset subsample generated the  random  forest
+        construction algorithm. Size is specified as a fraction of  total  dataset
+        size.
+
+        The default option is to use 50% of the dataset for training, 50% for  the
+        OOB estimates. You can decrease fraction F down to 10%, 1% or  even  below
+        in order to reduce overfitting.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object
+            F           -   fraction of the dataset to use, in (0,1] range. Values
+                            outside of this range will  be  silently  clipped.  At
+                            least one element is always selected for the  training
+                            set.
+
+        OUTPUT PARAMETERS:
+            S           -   decision forest builder
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfbuildersetsubsampleratio(decisionforestbuilder s,
+            double f,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(f), "dfbuildersetrndvarsfraction: F is INF or NAN");
+            s.rdfratio = Math.Max(f, math.machineepsilon);
+        }
+
+
+        /*************************************************************************
+        This function sets seed used by internal RNG for  random  subsampling  and
+        random selection of variable subsets.
+
+        By default, random seed is used, i.e. every time you build random  forest,
+        we seed generator with new value  obtained  from  system-wide  RNG.  Thus,
+        decision forest builder returns non-deterministic results. You can  change
+        such behavior by specyfing fixed positive seed value.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object
+            SeedVal     -   seed value:
+                            * positive values are used for seeding RNG with fixed
+                              seed, i.e. subsequent runs on same data will return
+                              same random forests
+                            * non-positive seed means that random seed is used
+                              for every run of builder, i.e. subsequent  runs  on
+                              same datasets will return slightly different random
+                              forests
+
+        OUTPUT PARAMETERS:
+            S           -   decision forest builder, see
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfbuildersetseed(decisionforestbuilder s,
+            int seedval,
+            alglib.xparams _params)
+        {
+            s.rdfglobalseed = seedval;
+        }
+
+
+        /*************************************************************************
+        This function sets random decision forest construction algorithm.
+
+        As for now, only one random forest construction algorithm is  supported  -
+        a dense "baseline" RDF algorithm.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object
+            AlgoType    -   algorithm type:
+                            * 0 = baseline dense RDF
+
+        OUTPUT PARAMETERS:
+            S           -   decision forest builder, see
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfbuildersetrdfalgo(decisionforestbuilder s,
+            int algotype,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(algotype==0, "dfbuildersetrdfalgo: unexpected algotype");
+            s.rdfalgo = algotype;
+        }
+
+
+        /*************************************************************************
+        This  function  sets  split  selection  algorithm  used  by random forests
+        classifier. You may choose several algorithms, with  different  speed  and
+        quality of the results.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object
+            SplitStrength-  split type:
+                            * 0 = split at the random position, fastest one
+                            * 1 = split at the middle of the range
+                            * 2 = strong split at the best point of the range (default)
+
+        OUTPUT PARAMETERS:
+            S           -   decision forest builder, see
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfbuildersetrdfsplitstrength(decisionforestbuilder s,
+            int splitstrength,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert((splitstrength==0 || splitstrength==1) || splitstrength==2, "dfbuildersetrdfsplitstrength: unexpected split type");
+            s.rdfsplitstrength = splitstrength;
+        }
+
+
+        /*************************************************************************
+        This function is an alias for dfbuilderpeekprogress(), left in ALGLIB  for
+        backward compatibility reasons.
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static double dfbuildergetprogress(decisionforestbuilder s,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = dfbuilderpeekprogress(s, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function is used to peek into random forest construction process from
+        other thread and get current progress indicator. It returns value in [0,1].
+
+        You can "peek" into decision forest builder from another thread.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object used  to  build  random
+                            forest in some other thread
+
+        RESULT:
+            progress value, in [0,1]
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static double dfbuilderpeekprogress(decisionforestbuilder s,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = s.rdfprogress/Math.Max(s.rdftotal, 1);
+            result = Math.Max(result, 0);
+            result = Math.Min(result, 1);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine builds random forest according to current settings,  using
+        dataset internally stored in the builder object. Dense algorithm is used.
+
+        NOTE: this   function   uses   dense  algorithm  for  forest  construction
+              independently from the dataset format (dense or sparse).
+
+        Default settings are used by the algorithm; you can tweak  them  with  the
+        help of the following functions:
+        * dfbuildersetrfactor() - to control a fraction of the  dataset  used  for
+          subsampling
+        * dfbuildersetrandomvars() - to control number of variables randomly chosen
+          for decision rule creation
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object
+            NTrees      -   NTrees>=1, number of trees to train
+
+        OUTPUT PARAMETERS:
+            DF          -   decision forest
+            Rep         -   report
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfbuilderbuildrandomforest(decisionforestbuilder s,
+            int ntrees,
+            decisionforest df,
+            dfreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int nvars = 0;
+            int nclasses = 0;
+            int npoints = 0;
+            int trnsize = 0;
+            int maxtreesize = 0;
+            dfworkbuf workbufseed = new dfworkbuf();
+            dfvotebuf votebufseed = new dfvotebuf();
+            dftreebuf treebufseed = new dftreebuf();
+
+            alglib.ap.assert(ntrees>=1, "DFBuilderBuildRandomForest: ntrees<1");
+            cleanreport(rep, _params);
+            npoints = s.npoints;
+            nvars = s.nvars;
+            nclasses = s.nclasses;
+            
+            //
+            // Set up progress counter
+            //
+            s.rdfprogress = 0;
+            s.rdftotal = ntrees;
+            
+            //
+            // Quick exit for empty dataset
+            //
+            if( s.dstype==-1 )
+            {
+                alglib.ap.assert(leafnodewidth==2, "DFBuilderBuildRandomForest: integrity check failed");
+                df.nvars = s.nvars;
+                df.nclasses = s.nclasses;
+                df.ntrees = 1;
+                df.bufsize = 1+leafnodewidth;
+                df.trees = new double[1+leafnodewidth];
+                df.trees[0] = 1+leafnodewidth;
+                df.trees[1] = -1;
+                df.trees[2] = 0.0;
+                dfcreatebuffer(df, df.buffer, _params);
+                return;
+            }
+            
+            //
+            // Analyze dataset statistics, perform preprocessing
+            //
+            analyzeandpreprocessdataset(s, _params);
+            
+            //
+            // Prepare "work", "vote" and "tree" pools
+            //
+            trnsize = (int)Math.Round(npoints*s.rdfratio);
+            trnsize = Math.Max(trnsize, 1);
+            trnsize = Math.Min(trnsize, npoints);
+            maxtreesize = 1+innernodewidth*(trnsize-1)+leafnodewidth*trnsize;
+            workbufseed.varpool = new int[nvars];
+            workbufseed.trnset = new int[trnsize];
+            workbufseed.oobset = new int[npoints-trnsize];
+            workbufseed.tmp0i = new int[npoints];
+            workbufseed.tmp1i = new int[npoints];
+            workbufseed.tmp0r = new double[npoints];
+            workbufseed.tmp1r = new double[npoints];
+            workbufseed.tmp2r = new double[npoints];
+            workbufseed.tmp3r = new double[npoints];
+            workbufseed.trnlabelsi = new int[npoints];
+            workbufseed.trnlabelsr = new double[npoints];
+            workbufseed.curvals = new double[npoints];
+            workbufseed.bestvals = new double[npoints];
+            workbufseed.classpriors = new int[nclasses];
+            workbufseed.classtotals0 = new int[nclasses];
+            workbufseed.classtotals1 = new int[nclasses];
+            workbufseed.classtotals01 = new int[2*nclasses];
+            workbufseed.treebuf = new double[maxtreesize];
+            workbufseed.trnsize = trnsize;
+            workbufseed.oobsize = npoints-trnsize;
+            votebufseed.trntotals = new double[npoints*nclasses];
+            votebufseed.oobtotals = new double[npoints*nclasses];
+            for(i=0; i<=npoints*nclasses-1; i++)
+            {
+                votebufseed.trntotals[i] = 0;
+                votebufseed.oobtotals[i] = 0;
+            }
+            votebufseed.trncounts = new int[npoints];
+            votebufseed.oobcounts = new int[npoints];
+            for(i=0; i<=npoints-1; i++)
+            {
+                votebufseed.trncounts[i] = 0;
+                votebufseed.oobcounts[i] = 0;
+            }
+            alglib.smp.ae_shared_pool_set_seed(s.workpool, workbufseed);
+            alglib.smp.ae_shared_pool_set_seed(s.votepool, votebufseed);
+            alglib.smp.ae_shared_pool_set_seed(s.treepool, treebufseed);
+            alglib.smp.ae_shared_pool_set_seed(s.treefactory, treebufseed);
+            
+            //
+            // Build trees (in parallel, if possible)
+            //
+            buildrandomtree(s, 0, ntrees, _params);
+            
+            //
+            // Merge trees and output result
+            //
+            mergetrees(s, df, _params);
+            
+            //
+            // Process voting results and output training set and OOB errors
+            //
+            processvotingresults(s, votebufseed, rep, _params);
+            
+            //
+            // Update progress counter
+            //
+            s.rdfprogress = s.rdftotal;
+            
+            //
+            // Prepare buffer
+            //
+            dfcreatebuffer(df, df.buffer, _params);
+        }
+
+
+        /*************************************************************************
+        Inference using decision forest
+
+        IMPORTANT: this  function  is  thread-unsafe  and  may   modify   internal
+                   structures of the model! You can not use same model  object for
+                   parallel evaluation from several threads.
+                   
+                   Use dftsprocess()  with  independent  thread-local  buffers  if
+                   you need thread-safe evaluation.
+
+        INPUT PARAMETERS:
+            DF      -   decision forest model
+            X       -   input vector,  array[NVars]
+            Y       -   possibly preallocated buffer, reallocated if too small
+
+        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,
+            alglib.xparams _params)
+        {
+            int offs = 0;
+            int i = 0;
+            double v = 0;
+            int i_ = 0;
+
+            
+            //
+            // Process
+            //
+            // Although comments above warn you about thread-unsafety of this
+            // function, it is de facto thread-safe. However, thread safety is
+            // an accidental side-effect of the specific inference algorithm
+            // being used. It may disappear in the future versions of the DF
+            // models, so you should NOT rely on it.
+            //
+            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, _params);
+                
+                //
+                // 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.
+
+        IMPORTANT: this  function  is  thread-unsafe  and  may   modify   internal
+                   structures of the model! You can not use same model  object for
+                   parallel evaluation from several threads.
+                   
+                   Use dftsprocess()  with  independent  thread-local  buffers  if
+                   you need thread-safe evaluation.
+
+          -- ALGLIB --
+             Copyright 28.02.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfprocessi(decisionforest df,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            y = new double[0];
+
+            dfprocess(df, x, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        This function returns first component of the  inferred  vector  (i.e.  one
+        with index #0).
+
+        It is a convenience wrapper for dfprocess() intended for either:
+        * 1-dimensional regression problems
+        * 2-class classification problems
+
+        In the former case this function returns inference result as scalar, which
+        is definitely more convenient that wrapping it as vector.  In  the  latter
+        case it returns probability of object belonging to class #0.
+
+        If you call it for anything different from two cases above, it  will  work
+        as defined, i.e. return y[0], although it is of less use in such cases.
+
+        IMPORTANT: this function is thread-unsafe and modifies internal structures
+                   of the model! You can not use same model  object  for  parallel
+                   evaluation from several threads.
+                   
+                   Use dftsprocess() with  independent  thread-local  buffers,  if
+                   you need thread-safe evaluation.
+
+        INPUT PARAMETERS:
+            Model   -   DF model
+            X       -   input vector,  array[0..NVars-1].
+
+        RESULT:
+            Y[0]
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static double dfprocess0(decisionforest model,
+            double[] x,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int i = 0;
+            int nvars = 0;
+
+            nvars = model.nvars;
+            for(i=0; i<=nvars-1; i++)
+            {
+                model.buffer.x[i] = x[i];
+            }
+            dfprocess(model, model.buffer.x, ref model.buffer.y, _params);
+            result = model.buffer.y[0];
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns most probable class number for an  input  X.  It  is
+        same as calling  dfprocess(model,x,y), then determining i=argmax(y[i]) and
+        returning i.
+
+        A class number in [0,NOut) range in returned for classification  problems,
+        -1 is returned when this function is called for regression problems.
+
+        IMPORTANT: this function is thread-unsafe and modifies internal structures
+                   of the model! You can not use same model  object  for  parallel
+                   evaluation from several threads.
+                   
+                   Use dftsprocess()  with independent  thread-local  buffers,  if
+                   you need thread-safe evaluation.
+
+        INPUT PARAMETERS:
+            Model   -   decision forest model
+            X       -   input vector,  array[0..NVars-1].
+
+        RESULT:
+            class number, -1 for regression tasks
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static int dfclassify(decisionforest model,
+            double[] x,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+            int nvars = 0;
+            int nout = 0;
+
+            if( model.nclasses<2 )
+            {
+                result = -1;
+                return result;
+            }
+            nvars = model.nvars;
+            nout = model.nclasses;
+            for(i=0; i<=nvars-1; i++)
+            {
+                model.buffer.x[i] = x[i];
+            }
+            dfprocess(model, model.buffer.x, ref model.buffer.y, _params);
+            result = 0;
+            for(i=1; i<=nout-1; i++)
+            {
+                if( model.buffer.y[i]>model.buffer.y[result] )
+                {
+                    result = i;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Inference using decision forest
+
+        Thread-safe procesing using external buffer for temporaries.
+
+        This function is thread-safe (i.e .  you  can  use  same  DF   model  from
+        multiple threads) as long as you use different buffer objects for different
+        threads.
+
+        INPUT PARAMETERS:
+            DF      -   decision forest model
+            Buf     -   buffer object, must be  allocated  specifically  for  this
+                        model with dfcreatebuffer().
+            X       -   input vector,  array[NVars]
+            Y       -   possibly preallocated buffer, reallocated if too small
+
+        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 dftsprocess(decisionforest df,
+            decisionforestbuffer buf,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Although docs warn you about thread-unsafety of the dfprocess()
+            // function, it is de facto thread-safe. However, thread safety is
+            // an accidental side-effect of the specific inference algorithm
+            // being used. It may disappear in the future versions of the DF
+            // models, so you should NOT rely on it.
+            //
+            dfprocess(df, x, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = (double)dfclserror(df, xy, npoints, _params)/(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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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_];
+            }
+            dfcreatebuffer(df2, df2.buffer, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfalloc(alglib.serializer s,
+            decisionforest forest,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfserialize(alglib.serializer s,
+            decisionforest forest,
+            alglib.xparams _params)
+        {
+            s.serialize_int(scodes.getrdfserializationcode(_params));
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 14.03.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dfunserialize(alglib.serializer s,
+            decisionforest forest,
+            alglib.xparams _params)
+        {
+            int i0 = 0;
+            int i1 = 0;
+
+            
+            //
+            // check correctness of header
+            //
+            i0 = s.unserialize_int();
+            alglib.ap.assert(i0==scodes.getrdfserializationcode(_params), "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, _params);
+            
+            //
+            // Prepare buffer
+            //
+            dfcreatebuffer(forest, forest.buffer, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine builds random decision forest.
+
+        --------- DEPRECATED VERSION! USE DECISION FOREST BUILDER OBJECT ---------
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine builds random decision forest.
+
+        --------- DEPRECATED VERSION! USE DECISION FOREST BUILDER OBJECT ---------
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        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,
+            alglib.xparams _params)
+        {
+            decisionforestbuilder builder = new decisionforestbuilder();
+            int i = 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;
+            dfbuildercreate(builder, _params);
+            dfbuildersetdataset(builder, xy, npoints, nvars, nclasses, _params);
+            dfbuildersetsubsampleratio(builder, (double)samplesize/(double)npoints, _params);
+            dfbuildersetrndvars(builder, nfeatures, _params);
+            dfbuilderbuildrandomforest(builder, ntrees, df, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Builds a range of random trees [TreeIdx0,TreeIdx1) using random forest
+        algorithm. Tree index is used to seed per-tree RNG.
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void buildrandomtree(decisionforestbuilder s,
+            int treeidx0,
+            int treeidx1,
+            alglib.xparams _params)
+        {
+            int treeidx = 0;
+            int i = 0;
+            int j = 0;
+            int npoints = 0;
+            int nvars = 0;
+            int nclasses = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+            dfworkbuf workbuf = null;
+            dfvotebuf votebuf = null;
+            dftreebuf treebuf = null;
+            int treesize = 0;
+            int varstoselect = 0;
+            int workingsetsize = 0;
+
+            
+            //
+            // Perform parallelization
+            //
+            if( treeidx1-treeidx0>1 )
+            {
+                if( _trypexec_buildrandomtree(s,treeidx0,treeidx1, _params) )
+                {
+                    return;
+                }
+                j = (treeidx1-treeidx0)/2;
+                buildrandomtree(s, treeidx0, treeidx0+j, _params);
+                buildrandomtree(s, treeidx0+j, treeidx1, _params);
+                return;
+            }
+            else
+            {
+                alglib.ap.assert(treeidx1-treeidx0==1, "RDF: integrity check failed");
+                treeidx = treeidx0;
+            }
+            
+            //
+            // Prepare
+            //
+            npoints = s.npoints;
+            nvars = s.nvars;
+            nclasses = s.nclasses;
+            if( s.rdfglobalseed>0 )
+            {
+                hqrnd.hqrndseed(s.rdfglobalseed, 1+treeidx, rs, _params);
+            }
+            else
+            {
+                hqrnd.hqrndseed(math.randominteger(30000), 1+treeidx, rs, _params);
+            }
+            
+            //
+            // Retrieve buffers.
+            //
+            alglib.smp.ae_shared_pool_retrieve(s.workpool, ref workbuf);
+            alglib.smp.ae_shared_pool_retrieve(s.votepool, ref votebuf);
+            
+            //
+            // Prepare everything for tree construction.
+            //
+            alglib.ap.assert(workbuf.trnsize>=1, "DForest: integrity check failed (34636)");
+            alglib.ap.assert(workbuf.oobsize>=0, "DForest: integrity check failed (45745)");
+            alglib.ap.assert(workbuf.trnsize+workbuf.oobsize==npoints, "DForest: integrity check failed (89415)");
+            workingsetsize = -1;
+            workbuf.varpoolsize = 0;
+            for(i=0; i<=nvars-1; i++)
+            {
+                if( (double)(s.dsmin[i])!=(double)(s.dsmax[i]) )
+                {
+                    workbuf.varpool[workbuf.varpoolsize] = i;
+                    apserv.inc(ref workbuf.varpoolsize, _params);
+                }
+            }
+            workingsetsize = workbuf.varpoolsize;
+            alglib.ap.assert(workingsetsize>=0, "DForest: integrity check failed (73f5)");
+            for(i=0; i<=npoints-1; i++)
+            {
+                workbuf.tmp0i[i] = i;
+            }
+            for(i=0; i<=workbuf.trnsize-1; i++)
+            {
+                j = hqrnd.hqrnduniformi(rs, npoints-i, _params);
+                apserv.swapelementsi(workbuf.tmp0i, i, i+j, _params);
+                workbuf.trnset[i] = workbuf.tmp0i[i];
+                if( nclasses>1 )
+                {
+                    workbuf.trnlabelsi[i] = s.dsival[workbuf.tmp0i[i]];
+                }
+                else
+                {
+                    workbuf.trnlabelsr[i] = s.dsrval[workbuf.tmp0i[i]];
+                }
+            }
+            for(i=0; i<=workbuf.oobsize-1; i++)
+            {
+                workbuf.oobset[i] = workbuf.tmp0i[workbuf.trnsize+i];
+            }
+            varstoselect = (int)Math.Round(Math.Sqrt(nvars));
+            if( (double)(s.rdfvars)>(double)(0) )
+            {
+                varstoselect = (int)Math.Round(s.rdfvars);
+            }
+            if( (double)(s.rdfvars)<(double)(0) )
+            {
+                varstoselect = (int)Math.Round(-(nvars*s.rdfvars));
+            }
+            varstoselect = Math.Max(varstoselect, 1);
+            varstoselect = Math.Min(varstoselect, nvars);
+            
+            //
+            // Perform recurrent construction
+            //
+            treesize = 1;
+            buildrandomtreerec(s, workbuf, workingsetsize, varstoselect, workbuf.treebuf, votebuf, rs, 0, workbuf.trnsize, 0, workbuf.oobsize, ref treesize, _params);
+            workbuf.treebuf[0] = treesize;
+            
+            //
+            // Store tree
+            //
+            alglib.smp.ae_shared_pool_retrieve(s.treefactory, ref treebuf);
+            treebuf.treebuf = new double[treesize];
+            for(i=0; i<=treesize-1; i++)
+            {
+                treebuf.treebuf[i] = workbuf.treebuf[i];
+            }
+            alglib.smp.ae_shared_pool_recycle(s.treepool, ref treebuf);
+            
+            //
+            // Return other buffers to appropriate pools
+            //
+            alglib.smp.ae_shared_pool_recycle(s.workpool, ref workbuf);
+            alglib.smp.ae_shared_pool_recycle(s.votepool, ref votebuf);
+            
+            //
+            // Update progress indicator
+            //
+            apserv.threadunsafeinc(ref s.rdfprogress, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_buildrandomtree(decisionforestbuilder s,
+            int treeidx0,
+            int treeidx1, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        Recurrent tree construction function using  caller-allocated  buffers  and
+        caller-initialized RNG.
+
+        Following iterms are processed:
+        * items [Idx0,Idx1) of WorkBuf.TrnSet
+        * items [OOBIdx0, OOBIdx1) of WorkBuf.OOBSet
+
+        TreeSize on input must be 1 (header element of the tree), on output it
+        contains size of the tree.
+
+        Variables from #0 to #WorkingSet-1 from WorkBuf.VarPool are used (for
+        block algorithm: blocks, not vars)
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void buildrandomtreerec(decisionforestbuilder s,
+            dfworkbuf workbuf,
+            int workingset,
+            int varstoselect,
+            double[] treebuf,
+            dfvotebuf votebuf,
+            hqrnd.hqrndstate rs,
+            int idx0,
+            int idx1,
+            int oobidx0,
+            int oobidx1,
+            ref int treesize,
+            alglib.xparams _params)
+        {
+            int npoints = 0;
+            int nclasses = 0;
+            int i = 0;
+            int j = 0;
+            int j0 = 0;
+            double v = 0;
+            bool labelsaresame = new bool();
+            int offs = 0;
+            int varbest = 0;
+            double splitbest = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int idxtrn = 0;
+            int idxoob = 0;
+
+            alglib.ap.assert(s.dstype==0, "not supported skbdgfsi!");
+            alglib.ap.assert(idx0<idx1, "BuildRandomTreeRec: integrity check failed (3445)");
+            alglib.ap.assert(oobidx0<=oobidx1, "BuildRandomTreeRec: integrity check failed (7452)");
+            npoints = s.npoints;
+            nclasses = s.nclasses;
+            
+            //
+            // Check labels: all same or not?
+            //
+            if( nclasses>1 )
+            {
+                labelsaresame = true;
+                for(i=0; i<=nclasses-1; i++)
+                {
+                    workbuf.classpriors[i] = 0;
+                }
+                j0 = workbuf.trnlabelsi[idx0];
+                for(i=idx0; i<=idx1-1; i++)
+                {
+                    j = workbuf.trnlabelsi[i];
+                    workbuf.classpriors[j] = workbuf.classpriors[j]+1;
+                    labelsaresame = labelsaresame && j0==j;
+                }
+            }
+            else
+            {
+                labelsaresame = false;
+            }
+            
+            //
+            // Leaf node
+            //
+            if( idx1-idx0==1 || labelsaresame )
+            {
+                if( nclasses==1 )
+                {
+                    outputleaf(s, workbuf, treebuf, votebuf, idx0, idx1, oobidx0, oobidx1, ref treesize, workbuf.trnlabelsr[idx0], _params);
+                }
+                else
+                {
+                    outputleaf(s, workbuf, treebuf, votebuf, idx0, idx1, oobidx0, oobidx1, ref treesize, workbuf.trnlabelsi[idx0], _params);
+                }
+                return;
+            }
+            
+            //
+            // Non-leaf node.
+            // Investigate possible splits.
+            //
+            alglib.ap.assert(s.rdfalgo==0, "BuildRandomForest: unexpected algo");
+            choosecurrentsplitdense(s, workbuf, ref workingset, varstoselect, rs, idx0, idx1, ref varbest, ref splitbest, _params);
+            if( varbest<0 )
+            {
+                
+                //
+                // No good split was found; make leaf (label is randomly chosen) and exit.
+                //
+                if( nclasses>1 )
+                {
+                    v = workbuf.trnlabelsi[idx0+hqrnd.hqrnduniformi(rs, idx1-idx0, _params)];
+                }
+                else
+                {
+                    v = workbuf.trnlabelsr[idx0+hqrnd.hqrnduniformi(rs, idx1-idx0, _params)];
+                }
+                outputleaf(s, workbuf, treebuf, votebuf, idx0, idx1, oobidx0, oobidx1, ref treesize, v, _params);
+                return;
+            }
+            
+            //
+            // Good split WAS found, we can perform it:
+            // * first, we split training set
+            // * then, we similarly split OOB set
+            // * and only then we perform recursive call
+            //
+            alglib.ap.assert(s.dstype==0, "not supported 54bfdh");
+            offs = npoints*varbest;
+            i1 = idx0;
+            i2 = idx1-1;
+            while( i1<=i2 )
+            {
+                
+                //
+                // Reorder indexes so that left partition is in [Idx0..I1),
+                // and right partition is in [I2+1..Idx1)
+                //
+                if( workbuf.bestvals[i1]<splitbest )
+                {
+                    i1 = i1+1;
+                    continue;
+                }
+                if( workbuf.bestvals[i2]>=splitbest )
+                {
+                    i2 = i2-1;
+                    continue;
+                }
+                j = workbuf.trnset[i1];
+                workbuf.trnset[i1] = workbuf.trnset[i2];
+                workbuf.trnset[i2] = j;
+                if( nclasses>1 )
+                {
+                    j = workbuf.trnlabelsi[i1];
+                    workbuf.trnlabelsi[i1] = workbuf.trnlabelsi[i2];
+                    workbuf.trnlabelsi[i2] = j;
+                }
+                else
+                {
+                    v = workbuf.trnlabelsr[i1];
+                    workbuf.trnlabelsr[i1] = workbuf.trnlabelsr[i2];
+                    workbuf.trnlabelsr[i2] = v;
+                }
+                i1 = i1+1;
+                i2 = i2-1;
+            }
+            alglib.ap.assert(i1==i2+1, "BuildRandomTreeRec: integrity check failed (45rds3)");
+            idxtrn = i1;
+            if( oobidx0<oobidx1 )
+            {
+                
+                //
+                // Unlike the training subset, the out-of-bag subset corresponding to the
+                // current sequence of decisions can be empty; thus, we have to explicitly
+                // handle situation of zero OOB subset.
+                //
+                i1 = oobidx0;
+                i2 = oobidx1-1;
+                while( i1<=i2 )
+                {
+                    
+                    //
+                    // Reorder indexes so that left partition is in [Idx0..I1),
+                    // and right partition is in [I2+1..Idx1)
+                    //
+                    if( s.dsdata[offs+workbuf.oobset[i1]]<splitbest )
+                    {
+                        i1 = i1+1;
+                        continue;
+                    }
+                    if( s.dsdata[offs+workbuf.oobset[i2]]>=splitbest )
+                    {
+                        i2 = i2-1;
+                        continue;
+                    }
+                    j = workbuf.oobset[i1];
+                    workbuf.oobset[i1] = workbuf.oobset[i2];
+                    workbuf.oobset[i2] = j;
+                    i1 = i1+1;
+                    i2 = i2-1;
+                }
+                alglib.ap.assert(i1==i2+1, "BuildRandomTreeRec: integrity check failed (643fs3)");
+                idxoob = i1;
+            }
+            else
+            {
+                idxoob = oobidx0;
+            }
+            treebuf[treesize] = varbest;
+            treebuf[treesize+1] = splitbest;
+            i = treesize;
+            treesize = treesize+innernodewidth;
+            buildrandomtreerec(s, workbuf, workingset, varstoselect, treebuf, votebuf, rs, idx0, idxtrn, oobidx0, idxoob, ref treesize, _params);
+            treebuf[i+2] = treesize;
+            buildrandomtreerec(s, workbuf, workingset, varstoselect, treebuf, votebuf, rs, idxtrn, idx1, idxoob, oobidx1, ref treesize, _params);
+        }
+
+
+        /*************************************************************************
+        Sets report fields to their default values
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void cleanreport(dfreport rep,
+            alglib.xparams _params)
+        {
+            rep.relclserror = 0;
+            rep.avgce = 0;
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.avgrelerror = 0;
+            rep.oobrelclserror = 0;
+            rep.oobavgce = 0;
+            rep.oobrmserror = 0;
+            rep.oobavgerror = 0;
+            rep.oobavgrelerror = 0;
+        }
+
+
+        /*************************************************************************
+        This function is a part of the recurrent tree construction function; it
+        selects variable for splitting according to current tree construction
+        algorithm.
+
+        Note: modifies VarsInPool, may decrease it if some variables become non-informative
+        and leave the pool.
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void choosecurrentsplitdense(decisionforestbuilder s,
+            dfworkbuf workbuf,
+            ref int varsinpool,
+            int varstoselect,
+            hqrnd.hqrndstate rs,
+            int idx0,
+            int idx1,
+            ref int varbest,
+            ref double splitbest,
+            alglib.xparams _params)
+        {
+            int npoints = 0;
+            double errbest = 0;
+            int varstried = 0;
+            int varcur = 0;
+            bool valuesaresame = new bool();
+            int offs = 0;
+            double split = 0;
+            int i = 0;
+            double v = 0;
+            double v0 = 0;
+            double currms = 0;
+            int info = 0;
+
+            varbest = 0;
+            splitbest = 0;
+
+            alglib.ap.assert(s.dstype==0, "sparsity is not supported 4terg!");
+            alglib.ap.assert(s.rdfalgo==0, "BuildRandomTreeRec: integrity check failed (1657)");
+            alglib.ap.assert(idx0<idx1, "BuildRandomTreeRec: integrity check failed (3445)");
+            npoints = s.npoints;
+            
+            //
+            // Select split according to dense direct RDF algorithm
+            //
+            varbest = -1;
+            errbest = math.maxrealnumber;
+            splitbest = 0;
+            varstried = 0;
+            while( varstried<=Math.Min(varstoselect, varsinpool)-1 )
+            {
+                
+                //
+                // select variables from pool
+                //
+                apserv.swapelementsi(workbuf.varpool, varstried, varstried+hqrnd.hqrnduniformi(rs, varsinpool-varstried, _params), _params);
+                varcur = workbuf.varpool[varstried];
+                
+                //
+                // Load variable values to working array.
+                // If all variable values are same, variable is excluded from pool and we re-run variable selection.
+                //
+                valuesaresame = true;
+                alglib.ap.assert(s.dstype==0, "not supported segsv34fs");
+                offs = npoints*varcur;
+                v0 = s.dsdata[offs+workbuf.trnset[idx0]];
+                for(i=idx0; i<=idx1-1; i++)
+                {
+                    v = s.dsdata[offs+workbuf.trnset[i]];
+                    workbuf.curvals[i] = v;
+                    valuesaresame = valuesaresame && v==v0;
+                }
+                if( valuesaresame )
+                {
+                    
+                    //
+                    // Variable does not change across current subset.
+                    // Exclude variable from pool, go to the next iteration.
+                    // VarsTried is not increased.
+                    //
+                    // NOTE: it is essential that updated VarsInPool is passed
+                    //       down to children but not up to caller - it is
+                    //       possible that one level higher this variable is
+                    //       not-fixed.
+                    //
+                    apserv.swapelementsi(workbuf.varpool, varstried, varsinpool-1, _params);
+                    varsinpool = varsinpool-1;
+                    continue;
+                }
+                
+                //
+                // Now we are ready to infer the split
+                //
+                evaluatedensesplit(s, workbuf, rs, varcur, idx0, idx1, ref info, ref split, ref currms, _params);
+                if( info>0 && (varbest<0 || (double)(currms)<=(double)(errbest)) )
+                {
+                    errbest = currms;
+                    varbest = varcur;
+                    splitbest = split;
+                    for(i=idx0; i<=idx1-1; i++)
+                    {
+                        workbuf.bestvals[i] = workbuf.curvals[i];
+                    }
+                }
+                
+                //
+                // Next iteration
+                //
+                varstried = varstried+1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function performs split on some specific dense variable whose values
+        are stored in WorkBuf.CurVals[Idx0,Idx1) and labels are stored in
+        WorkBuf.TrnLabelsR/I[Idx0,Idx1).
+
+        It returns split value and associated RMS error. It is responsibility of
+        the caller to make sure that variable has at least two distinct values,
+        i.e. it is possible to make a split.
+
+        Precomputed values of following fields of WorkBuf are used:
+        * ClassPriors
+
+        Following fields of WorkBuf are used as temporaries:
+        * ClassTotals0,1,01
+        * Tmp0I, Tmp1I, Tmp0R, Tmp1R, Tmp2R, Tmp3R
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void evaluatedensesplit(decisionforestbuilder s,
+            dfworkbuf workbuf,
+            hqrnd.hqrndstate rs,
+            int splitvar,
+            int idx0,
+            int idx1,
+            ref int info,
+            ref double split,
+            ref double rms,
+            alglib.xparams _params)
+        {
+            int nclasses = 0;
+            int i = 0;
+            int j = 0;
+            int k0 = 0;
+            int k1 = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            int sl = 0;
+            int sr = 0;
+
+            info = 0;
+            split = 0;
+            rms = 0;
+
+            alglib.ap.assert(idx0<idx1, "BuildRandomTreeRec: integrity check failed (8754)");
+            nclasses = s.nclasses;
+            if( s.dsbinary[splitvar] )
+            {
+                
+                //
+                // Try simple binary split, if possible
+                // Split can be inferred from minimum/maximum values, just calculate RMS error
+                //
+                info = 1;
+                split = getsplit(s, s.dsmin[splitvar], s.dsmax[splitvar], rs, _params);
+                if( nclasses>1 )
+                {
+                    
+                    //
+                    // Classification problem
+                    //
+                    for(j=0; j<=nclasses-1; j++)
+                    {
+                        workbuf.classtotals0[j] = 0;
+                    }
+                    sl = 0;
+                    for(i=idx0; i<=idx1-1; i++)
+                    {
+                        if( workbuf.curvals[i]<split )
+                        {
+                            j = workbuf.trnlabelsi[i];
+                            workbuf.classtotals0[j] = workbuf.classtotals0[j]+1;
+                            sl = sl+1;
+                        }
+                    }
+                    sr = idx1-idx0-sl;
+                    alglib.ap.assert(sl!=0 && sr!=0, "BuildRandomTreeRec: something strange, impossible failure!");
+                    v0 = (double)1/(double)sl;
+                    v1 = (double)1/(double)sr;
+                    rms = 0;
+                    for(j=0; j<=nclasses-1; j++)
+                    {
+                        k0 = workbuf.classtotals0[j];
+                        k1 = workbuf.classpriors[j]-k0;
+                        rms = rms+k0*(1-v0*k0)+k1*(1-v1*k1);
+                    }
+                    rms = Math.Sqrt(rms/(nclasses*(idx1-idx0+1)));
+                }
+                else
+                {
+                    
+                    //
+                    // regression-specific code
+                    //
+                    sl = 0;
+                    sr = 0;
+                    v1 = 0;
+                    v2 = 0;
+                    for(j=idx0; j<=idx1-1; j++)
+                    {
+                        if( workbuf.curvals[j]<split )
+                        {
+                            v1 = v1+workbuf.trnlabelsr[j];
+                            sl = sl+1;
+                        }
+                        else
+                        {
+                            v2 = v2+workbuf.trnlabelsr[j];
+                            sr = sr+1;
+                        }
+                    }
+                    alglib.ap.assert(sl!=0 && sr!=0, "BuildRandomTreeRec: something strange, impossible failure!");
+                    v1 = v1/sl;
+                    v2 = v2/sr;
+                    rms = 0;
+                    for(j=0; j<=idx1-idx0-1; j++)
+                    {
+                        v = workbuf.trnlabelsr[idx0+j];
+                        if( v<split )
+                        {
+                            v = v-v1;
+                        }
+                        else
+                        {
+                            v = v-v2;
+                        }
+                        rms = rms+v*v;
+                    }
+                    rms = Math.Sqrt(rms/(idx1-idx0+1));
+                }
+            }
+            else
+            {
+                
+                //
+                // General split
+                //
+                info = 0;
+                if( nclasses>1 )
+                {
+                    for(i=0; i<=idx1-idx0-1; i++)
+                    {
+                        workbuf.tmp0r[i] = workbuf.curvals[idx0+i];
+                        workbuf.tmp0i[i] = workbuf.trnlabelsi[idx0+i];
+                    }
+                    classifiersplit(s, workbuf, ref workbuf.tmp0r, ref workbuf.tmp0i, idx1-idx0, rs, ref info, ref split, ref rms, ref workbuf.tmp1r, ref workbuf.tmp1i, _params);
+                }
+                else
+                {
+                    for(i=0; i<=idx1-idx0-1; i++)
+                    {
+                        workbuf.tmp0r[i] = workbuf.curvals[idx0+i];
+                        workbuf.tmp1r[i] = workbuf.trnlabelsr[idx0+i];
+                    }
+                    regressionsplit(s, workbuf, ref workbuf.tmp0r, ref workbuf.tmp1r, idx1-idx0, ref info, ref split, ref rms, ref workbuf.tmp2r, ref workbuf.tmp3r, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Classifier split
+        *************************************************************************/
+        private static void classifiersplit(decisionforestbuilder s,
+            dfworkbuf workbuf,
+            ref double[] x,
+            ref int[] c,
+            int n,
+            hqrnd.hqrndstate rs,
+            ref int info,
+            ref double threshold,
+            ref double e,
+            ref double[] sortrbuf,
+            ref int[] sortibuf,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int n0 = 0;
+            int n0prev = 0;
+            double v = 0;
+            int advanceby = 0;
+            double rms = 0;
+            int k0 = 0;
+            int k1 = 0;
+            double v0 = 0;
+            double v1 = 0;
+            int nclasses = 0;
+            double vmin = 0;
+            double vmax = 0;
+
+            info = 0;
+            threshold = 0;
+            e = 0;
+
+            alglib.ap.assert((s.rdfsplitstrength==0 || s.rdfsplitstrength==1) || s.rdfsplitstrength==2, "RDF: unexpected split type at ClassifierSplit()");
+            nclasses = s.nclasses;
+            advanceby = 1;
+            if( n>=20 )
+            {
+                advanceby = Math.Max(2, (int)Math.Round(n*0.05));
+            }
+            info = -1;
+            threshold = 0;
+            e = math.maxrealnumber;
+            
+            //
+            // Random split
+            //
+            if( s.rdfsplitstrength==0 )
+            {
+                
+                //
+                // Evaluate minimum, maximum and randomly selected values
+                //
+                vmin = x[0];
+                vmax = x[0];
+                for(i=1; i<=n-1; i++)
+                {
+                    v = x[i];
+                    if( v<vmin )
+                    {
+                        vmin = v;
+                    }
+                    if( v>vmax )
+                    {
+                        vmax = v;
+                    }
+                }
+                if( (double)(vmin)==(double)(vmax) )
+                {
+                    return;
+                }
+                v = x[hqrnd.hqrnduniformi(rs, n, _params)];
+                if( (double)(v)==(double)(vmin) )
+                {
+                    v = vmax;
+                }
+                
+                //
+                // Calculate RMS error associated with the split
+                //
+                for(i=0; i<=nclasses-1; i++)
+                {
+                    workbuf.classtotals0[i] = 0;
+                }
+                n0 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( x[i]<v )
+                    {
+                        k = c[i];
+                        workbuf.classtotals0[k] = workbuf.classtotals0[k]+1;
+                        n0 = n0+1;
+                    }
+                }
+                alglib.ap.assert(n0>0 && n0<n, "RDF: critical integrity check failed at ClassifierSplit()");
+                v0 = (double)1/(double)n0;
+                v1 = (double)1/(double)(n-n0);
+                rms = 0;
+                for(j=0; j<=nclasses-1; j++)
+                {
+                    k0 = workbuf.classtotals0[j];
+                    k1 = workbuf.classpriors[j]-k0;
+                    rms = rms+k0*(1-v0*k0)+k1*(1-v1*k1);
+                }
+                threshold = v;
+                info = 1;
+                e = rms;
+                return;
+            }
+            
+            //
+            // Stronger splits which require us to sort the data
+            // Quick check for degeneracy
+            //
+            tsort.tagsortfasti(ref x, ref c, ref sortrbuf, ref sortibuf, n, _params);
+            v = 0.5*(x[0]+x[n-1]);
+            if( !((double)(x[0])<(double)(v) && (double)(v)<(double)(x[n-1])) )
+            {
+                return;
+            }
+            
+            //
+            // Split at the middle
+            //
+            if( s.rdfsplitstrength==1 )
+            {
+                
+                //
+                // Select split position
+                //
+                vmin = x[0];
+                vmax = x[n-1];
+                v = x[n/2];
+                if( (double)(v)==(double)(vmin) )
+                {
+                    v = vmin+0.001*(vmax-vmin);
+                }
+                if( (double)(v)==(double)(vmin) )
+                {
+                    v = vmax;
+                }
+                
+                //
+                // Calculate RMS error associated with the split
+                //
+                for(i=0; i<=nclasses-1; i++)
+                {
+                    workbuf.classtotals0[i] = 0;
+                }
+                n0 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( x[i]<v )
+                    {
+                        k = c[i];
+                        workbuf.classtotals0[k] = workbuf.classtotals0[k]+1;
+                        n0 = n0+1;
+                    }
+                }
+                alglib.ap.assert(n0>0 && n0<n, "RDF: critical integrity check failed at ClassifierSplit()");
+                v0 = (double)1/(double)n0;
+                v1 = (double)1/(double)(n-n0);
+                rms = 0;
+                for(j=0; j<=nclasses-1; j++)
+                {
+                    k0 = workbuf.classtotals0[j];
+                    k1 = workbuf.classpriors[j]-k0;
+                    rms = rms+k0*(1-v0*k0)+k1*(1-v1*k1);
+                }
+                threshold = v;
+                info = 1;
+                e = rms;
+                return;
+            }
+            
+            //
+            // Strong split
+            //
+            if( s.rdfsplitstrength==2 )
+            {
+                
+                //
+                // Prepare initial split.
+                // Evaluate current split, prepare next one, repeat.
+                //
+                for(i=0; i<=nclasses-1; i++)
+                {
+                    workbuf.classtotals0[i] = 0;
+                }
+                n0 = 1;
+                while( n0<n && x[n0]==x[n0-1] )
+                {
+                    n0 = n0+1;
+                }
+                alglib.ap.assert(n0<n, "RDF: critical integrity check failed in ClassifierSplit()");
+                for(i=0; i<=n0-1; i++)
+                {
+                    k = c[i];
+                    workbuf.classtotals0[k] = workbuf.classtotals0[k]+1;
+                }
+                info = -1;
+                threshold = x[n-1];
+                e = math.maxrealnumber;
+                while( n0<n )
+                {
+                    
+                    //
+                    // RMS error associated with current split
+                    //
+                    v0 = (double)1/(double)n0;
+                    v1 = (double)1/(double)(n-n0);
+                    rms = 0;
+                    for(j=0; j<=nclasses-1; j++)
+                    {
+                        k0 = workbuf.classtotals0[j];
+                        k1 = workbuf.classpriors[j]-k0;
+                        rms = rms+k0*(1-v0*k0)+k1*(1-v1*k1);
+                    }
+                    if( info<0 || rms<e )
+                    {
+                        info = 1;
+                        e = rms;
+                        threshold = 0.5*(x[n0-1]+x[n0]);
+                        if( threshold<=x[n0-1] )
+                        {
+                            threshold = x[n0];
+                        }
+                    }
+                    
+                    //
+                    // Advance
+                    //
+                    n0prev = n0;
+                    while( n0<n && n0-n0prev<advanceby )
+                    {
+                        v = x[n0];
+                        while( n0<n && x[n0]==v )
+                        {
+                            k = c[n0];
+                            workbuf.classtotals0[k] = workbuf.classtotals0[k]+1;
+                            n0 = n0+1;
+                        }
+                    }
+                }
+                if( info>0 )
+                {
+                    e = Math.Sqrt(e/(nclasses*n));
+                }
+                return;
+            }
+            alglib.ap.assert(false, "RDF: ClassifierSplit(), critical error");
+        }
+
+
+        /*************************************************************************
+        Regression model split
+        *************************************************************************/
+        private static void regressionsplit(decisionforestbuilder s,
+            dfworkbuf workbuf,
+            ref double[] x,
+            ref double[] y,
+            int n,
+            ref int info,
+            ref double threshold,
+            ref double e,
+            ref double[] sortrbuf,
+            ref double[] sortrbuf2,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double vmin = 0;
+            double vmax = 0;
+            double bnd01 = 0;
+            double bnd12 = 0;
+            double bnd23 = 0;
+            int total0 = 0;
+            int total1 = 0;
+            int total2 = 0;
+            int total3 = 0;
+            int cnt0 = 0;
+            int cnt1 = 0;
+            int cnt2 = 0;
+            int cnt3 = 0;
+            int n0 = 0;
+            int advanceby = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double rms = 0;
+            int n0prev = 0;
+            int k0 = 0;
+            int k1 = 0;
+
+            info = 0;
+            threshold = 0;
+            e = 0;
+
+            advanceby = 1;
+            if( n>=20 )
+            {
+                advanceby = Math.Max(2, (int)Math.Round(n*0.05));
+            }
+            
+            //
+            // Sort data
+            // Quick check for degeneracy
+            //
+            tsort.tagsortfastr(ref x, ref y, ref sortrbuf, ref sortrbuf2, n, _params);
+            v = 0.5*(x[0]+x[n-1]);
+            if( !((double)(x[0])<(double)(v) && (double)(v)<(double)(x[n-1])) )
+            {
+                info = -1;
+                threshold = x[n-1];
+                e = math.maxrealnumber;
+                return;
+            }
+            
+            //
+            // Prepare initial split.
+            // Evaluate current split, prepare next one, repeat.
+            //
+            vmin = y[0];
+            vmax = y[0];
+            for(i=1; i<=n-1; i++)
+            {
+                v = y[i];
+                if( v<vmin )
+                {
+                    vmin = v;
+                }
+                if( v>vmax )
+                {
+                    vmax = v;
+                }
+            }
+            bnd12 = 0.5*(vmin+vmax);
+            bnd01 = 0.5*(vmin+bnd12);
+            bnd23 = 0.5*(vmax+bnd12);
+            total0 = 0;
+            total1 = 0;
+            total2 = 0;
+            total3 = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = y[i];
+                if( v<bnd12 )
+                {
+                    if( v<bnd01 )
+                    {
+                        total0 = total0+1;
+                    }
+                    else
+                    {
+                        total1 = total1+1;
+                    }
+                }
+                else
+                {
+                    if( v<bnd23 )
+                    {
+                        total2 = total2+1;
+                    }
+                    else
+                    {
+                        total3 = total3+1;
+                    }
+                }
+            }
+            n0 = 1;
+            while( n0<n && x[n0]==x[n0-1] )
+            {
+                n0 = n0+1;
+            }
+            alglib.ap.assert(n0<n, "RDF: critical integrity check failed in ClassifierSplit()");
+            cnt0 = 0;
+            cnt1 = 0;
+            cnt2 = 0;
+            cnt3 = 0;
+            for(i=0; i<=n0-1; i++)
+            {
+                v = y[i];
+                if( v<bnd12 )
+                {
+                    if( v<bnd01 )
+                    {
+                        cnt0 = cnt0+1;
+                    }
+                    else
+                    {
+                        cnt1 = cnt1+1;
+                    }
+                }
+                else
+                {
+                    if( v<bnd23 )
+                    {
+                        cnt2 = cnt2+1;
+                    }
+                    else
+                    {
+                        cnt3 = cnt3+1;
+                    }
+                }
+            }
+            info = -1;
+            threshold = x[n-1];
+            e = math.maxrealnumber;
+            while( n0<n )
+            {
+                
+                //
+                // RMS error associated with current split
+                //
+                v0 = (double)1/(double)n0;
+                v1 = (double)1/(double)(n-n0);
+                rms = 0;
+                k0 = cnt0;
+                k1 = total0-cnt0;
+                rms = rms+k0*(1-v0*k0)+k1*(1-v1*k1);
+                k0 = cnt1;
+                k1 = total1-cnt1;
+                rms = rms+k0*(1-v0*k0)+k1*(1-v1*k1);
+                k0 = cnt2;
+                k1 = total2-cnt2;
+                rms = rms+k0*(1-v0*k0)+k1*(1-v1*k1);
+                k0 = cnt3;
+                k1 = total3-cnt3;
+                rms = rms+k0*(1-v0*k0)+k1*(1-v1*k1);
+                if( info<0 || rms<e )
+                {
+                    info = 1;
+                    e = rms;
+                    threshold = 0.5*(x[n0-1]+x[n0]);
+                    if( threshold<=x[n0-1] )
+                    {
+                        threshold = x[n0];
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                n0prev = n0;
+                while( n0<n && n0-n0prev<advanceby )
+                {
+                    v0 = x[n0];
+                    while( n0<n && x[n0]==v0 )
+                    {
+                        v = y[n0];
+                        if( v<bnd12 )
+                        {
+                            if( v<bnd01 )
+                            {
+                                cnt0 = cnt0+1;
+                            }
+                            else
+                            {
+                                cnt1 = cnt1+1;
+                            }
+                        }
+                        else
+                        {
+                            if( v<bnd23 )
+                            {
+                                cnt2 = cnt2+1;
+                            }
+                            else
+                            {
+                                cnt3 = cnt3+1;
+                            }
+                        }
+                        n0 = n0+1;
+                    }
+                }
+            }
+            if( info>0 )
+            {
+                e = Math.Sqrt(e/(4*n));
+            }
+        }
+
+
+        /*************************************************************************
+        Returns split: either deterministic split at the middle of [A,B], or randomly
+        chosen split.
+
+        It is guaranteed that A<Split<=B.
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static double getsplit(decisionforestbuilder s,
+            double a,
+            double b,
+            hqrnd.hqrndstate rs,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = 0.5*(a+b);
+            if( (double)(result)<=(double)(a) )
+            {
+                result = b;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Outputs leaf to the tree
+
+        Following items of TRN and OOB sets are updated in the voting buffer:
+        * items [Idx0,Idx1) of WorkBuf.TrnSet
+        * items [OOBIdx0, OOBIdx1) of WorkBuf.OOBSet
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void outputleaf(decisionforestbuilder s,
+            dfworkbuf workbuf,
+            double[] treebuf,
+            dfvotebuf votebuf,
+            int idx0,
+            int idx1,
+            int oobidx0,
+            int oobidx1,
+            ref int treesize,
+            double leafval,
+            alglib.xparams _params)
+        {
+            int leafvali = 0;
+            int nclasses = 0;
+            int i = 0;
+            int j = 0;
+
+            nclasses = s.nclasses;
+            if( nclasses==1 )
+            {
+                
+                //
+                // Store split to the tree
+                //
+                treebuf[treesize] = -1;
+                treebuf[treesize+1] = leafval;
+                
+                //
+                // Update training and OOB voting stats
+                //
+                for(i=idx0; i<=idx1-1; i++)
+                {
+                    j = workbuf.trnset[i];
+                    votebuf.trntotals[j] = votebuf.trntotals[j]+leafval;
+                    votebuf.trncounts[j] = votebuf.trncounts[j]+1;
+                }
+                for(i=oobidx0; i<=oobidx1-1; i++)
+                {
+                    j = workbuf.oobset[i];
+                    votebuf.oobtotals[j] = votebuf.oobtotals[j]+leafval;
+                    votebuf.oobcounts[j] = votebuf.oobcounts[j]+1;
+                }
+            }
+            else
+            {
+                
+                //
+                // Store split to the tree
+                //
+                treebuf[treesize] = -1;
+                treebuf[treesize+1] = leafval;
+                
+                //
+                // Update training and OOB voting stats
+                //
+                leafvali = (int)Math.Round(leafval);
+                for(i=idx0; i<=idx1-1; i++)
+                {
+                    j = workbuf.trnset[i];
+                    votebuf.trntotals[j*nclasses+leafvali] = votebuf.trntotals[j*nclasses+leafvali]+1;
+                    votebuf.trncounts[j] = votebuf.trncounts[j]+1;
+                }
+                for(i=oobidx0; i<=oobidx1-1; i++)
+                {
+                    j = workbuf.oobset[i];
+                    votebuf.oobtotals[j*nclasses+leafvali] = votebuf.oobtotals[j*nclasses+leafvali]+1;
+                    votebuf.oobcounts[j] = votebuf.oobcounts[j]+1;
+                }
+            }
+            treesize = treesize+leafnodewidth;
+        }
+
+
+        /*************************************************************************
+        This function performs generic and algorithm-specific preprocessing of the
+        dataset
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void analyzeandpreprocessdataset(decisionforestbuilder s,
+            alglib.xparams _params)
+        {
+            int nvars = 0;
+            int nclasses = 0;
+            int npoints = 0;
+            int i = 0;
+            int j = 0;
+            bool isbinary = new bool();
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+
+            alglib.ap.assert(s.dstype==0, "no sparsity");
+            npoints = s.npoints;
+            nvars = s.nvars;
+            nclasses = s.nclasses;
+            
+            //
+            // seed local RNG
+            //
+            if( s.rdfglobalseed>0 )
+            {
+                hqrnd.hqrndseed(s.rdfglobalseed, 3532, rs, _params);
+            }
+            else
+            {
+                hqrnd.hqrndseed(math.randominteger(30000), 3532, rs, _params);
+            }
+            
+            //
+            // Generic processing
+            //
+            alglib.ap.assert(npoints>=1, "BuildRandomForest: integrity check failed");
+            apserv.rvectorsetlengthatleast(ref s.dsmin, nvars, _params);
+            apserv.rvectorsetlengthatleast(ref s.dsmax, nvars, _params);
+            apserv.bvectorsetlengthatleast(ref s.dsbinary, nvars, _params);
+            for(i=0; i<=nvars-1; i++)
+            {
+                v0 = s.dsdata[i*npoints+0];
+                v1 = s.dsdata[i*npoints+0];
+                for(j=1; j<=npoints-1; j++)
+                {
+                    v = s.dsdata[i*npoints+j];
+                    if( v<v0 )
+                    {
+                        v0 = v;
+                    }
+                    if( v>v1 )
+                    {
+                        v1 = v;
+                    }
+                }
+                s.dsmin[i] = v0;
+                s.dsmax[i] = v1;
+                alglib.ap.assert((double)(v0)<=(double)(v1), "BuildRandomForest: strange integrity check failure");
+                isbinary = true;
+                for(j=0; j<=npoints-1; j++)
+                {
+                    v = s.dsdata[i*npoints+j];
+                    isbinary = isbinary && (v==v0 || v==v1);
+                }
+                s.dsbinary[i] = isbinary;
+            }
+            if( nclasses==1 )
+            {
+                s.dsravg = 0;
+                for(i=0; i<=npoints-1; i++)
+                {
+                    s.dsravg = s.dsravg+s.dsrval[i];
+                }
+                s.dsravg = s.dsravg/npoints;
+            }
+            else
+            {
+                apserv.ivectorsetlengthatleast(ref s.dsctotals, nclasses, _params);
+                for(i=0; i<=nclasses-1; i++)
+                {
+                    s.dsctotals[i] = 0;
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    s.dsctotals[s.dsival[i]] = s.dsctotals[s.dsival[i]]+1;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function merges together trees generated during training and outputs
+        it to the decision forest.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object
+            NTrees      -   NTrees>=1, number of trees to train
+
+        OUTPUT PARAMETERS:
+            DF          -   decision forest
+            Rep         -   report
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void mergetrees(decisionforestbuilder s,
+            decisionforest df,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int cursize = 0;
+            int offs = 0;
+            dftreebuf tree = null;
+
+            df.nvars = s.nvars;
+            df.nclasses = s.nclasses;
+            df.bufsize = 0;
+            df.ntrees = 0;
+            alglib.smp.ae_shared_pool_first_recycled(s.treepool, ref tree);
+            while( tree!=null )
+            {
+                df.bufsize = df.bufsize+(int)Math.Round(tree.treebuf[0]);
+                df.ntrees = df.ntrees+1;
+                alglib.smp.ae_shared_pool_next_recycled(s.treepool, ref tree);
+            }
+            df.trees = new double[df.bufsize];
+            alglib.smp.ae_shared_pool_first_recycled(s.treepool, ref tree);
+            offs = 0;
+            while( tree!=null )
+            {
+                cursize = (int)Math.Round(tree.treebuf[0]);
+                for(i=0; i<=cursize-1; i++)
+                {
+                    df.trees[offs+i] = tree.treebuf[i];
+                }
+                offs = offs+cursize;
+                alglib.smp.ae_shared_pool_next_recycled(s.treepool, ref tree);
+            }
+        }
+
+
+        /*************************************************************************
+        This function post-processes voting array and calculates TRN and OOB errors.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object
+            Buf         -   possibly preallocated vote buffer, its contents is
+                            overwritten by this function
+
+        OUTPUT PARAMETERS:
+            Rep         -   report fields corresponding to errors are updated
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void processvotingresults(decisionforestbuilder s,
+            dfvotebuf buf,
+            dfreport rep,
+            alglib.xparams _params)
+        {
+            dfvotebuf vote = null;
+            int nvars = 0;
+            int nclasses = 0;
+            int npoints = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int k1 = 0;
+            double v = 0;
+            int avgrelcnt = 0;
+            int oobavgrelcnt = 0;
+
+            npoints = s.npoints;
+            nvars = s.nvars;
+            nclasses = s.nclasses;
+            alglib.ap.assert(npoints>0, "DFOREST: integrity check failed");
+            alglib.ap.assert(nvars>0, "DFOREST: integrity check failed");
+            alglib.ap.assert(nclasses>0, "DFOREST: integrity check failed");
+            
+            //
+            // Prepare vote buffer
+            //
+            apserv.rvectorsetlengthatleast(ref buf.trntotals, npoints*nclasses, _params);
+            apserv.rvectorsetlengthatleast(ref buf.oobtotals, npoints*nclasses, _params);
+            for(i=0; i<=npoints*nclasses-1; i++)
+            {
+                buf.trntotals[i] = 0;
+                buf.oobtotals[i] = 0;
+            }
+            apserv.ivectorsetlengthatleast(ref buf.trncounts, npoints, _params);
+            apserv.ivectorsetlengthatleast(ref buf.oobcounts, npoints, _params);
+            for(i=0; i<=npoints-1; i++)
+            {
+                buf.trncounts[i] = 0;
+                buf.oobcounts[i] = 0;
+            }
+            
+            //
+            // Merge voting arrays
+            //
+            alglib.smp.ae_shared_pool_first_recycled(s.votepool, ref vote);
+            while( vote!=null )
+            {
+                for(i=0; i<=npoints*nclasses-1; i++)
+                {
+                    buf.trntotals[i] = buf.trntotals[i]+vote.trntotals[i]+vote.oobtotals[i];
+                    buf.oobtotals[i] = buf.oobtotals[i]+vote.oobtotals[i];
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    buf.trncounts[i] = buf.trncounts[i]+vote.trncounts[i]+vote.oobcounts[i];
+                    buf.oobcounts[i] = buf.oobcounts[i]+vote.oobcounts[i];
+                }
+                alglib.smp.ae_shared_pool_next_recycled(s.votepool, ref vote);
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                v = 1/apserv.coalesce(buf.trncounts[i], 1, _params);
+                for(j=0; j<=nclasses-1; j++)
+                {
+                    buf.trntotals[i*nclasses+j] = buf.trntotals[i*nclasses+j]*v;
+                }
+                v = 1/apserv.coalesce(buf.oobcounts[i], 1, _params);
+                for(j=0; j<=nclasses-1; j++)
+                {
+                    buf.oobtotals[i*nclasses+j] = buf.oobtotals[i*nclasses+j]*v;
+                }
+            }
+            
+            //
+            // Use aggregated voting data to output error metrics
+            //
+            avgrelcnt = 0;
+            oobavgrelcnt = 0;
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.avgrelerror = 0;
+            rep.relclserror = 0;
+            rep.avgce = 0;
+            rep.oobrmserror = 0;
+            rep.oobavgerror = 0;
+            rep.oobavgrelerror = 0;
+            rep.oobrelclserror = 0;
+            rep.oobavgce = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                if( nclasses>1 )
+                {
+                    
+                    //
+                    // classification-specific code
+                    //
+                    k = s.dsival[i];
+                    for(j=0; j<=nclasses-1; j++)
+                    {
+                        v = buf.trntotals[i*nclasses+j];
+                        if( j==k )
+                        {
+                            rep.avgce = rep.avgce-Math.Log(apserv.coalesce(v, math.minrealnumber, _params));
+                            rep.rmserror = rep.rmserror+math.sqr(v-1);
+                            rep.avgerror = rep.avgerror+Math.Abs(v-1);
+                            rep.avgrelerror = rep.avgrelerror+Math.Abs(v-1);
+                            apserv.inc(ref avgrelcnt, _params);
+                        }
+                        else
+                        {
+                            rep.rmserror = rep.rmserror+math.sqr(v);
+                            rep.avgerror = rep.avgerror+Math.Abs(v);
+                        }
+                        v = buf.oobtotals[i*nclasses+j];
+                        if( j==k )
+                        {
+                            rep.oobavgce = rep.oobavgce-Math.Log(apserv.coalesce(v, math.minrealnumber, _params));
+                            rep.oobrmserror = rep.oobrmserror+math.sqr(v-1);
+                            rep.oobavgerror = rep.oobavgerror+Math.Abs(v-1);
+                            rep.oobavgrelerror = rep.oobavgrelerror+Math.Abs(v-1);
+                            apserv.inc(ref oobavgrelcnt, _params);
+                        }
+                        else
+                        {
+                            rep.oobrmserror = rep.oobrmserror+math.sqr(v);
+                            rep.oobavgerror = rep.oobavgerror+Math.Abs(v);
+                        }
+                    }
+                    
+                    //
+                    // Classification errors are handled separately
+                    //
+                    k1 = 0;
+                    for(j=1; j<=nclasses-1; j++)
+                    {
+                        if( buf.trntotals[i*nclasses+j]>buf.trntotals[i*nclasses+k1] )
+                        {
+                            k1 = j;
+                        }
+                    }
+                    if( k1!=k )
+                    {
+                        rep.relclserror = rep.relclserror+1;
+                    }
+                    k1 = 0;
+                    for(j=1; j<=nclasses-1; j++)
+                    {
+                        if( buf.oobtotals[i*nclasses+j]>buf.oobtotals[i*nclasses+k1] )
+                        {
+                            k1 = j;
+                        }
+                    }
+                    if( k1!=k )
+                    {
+                        rep.oobrelclserror = rep.oobrelclserror+1;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // regression-specific code
+                    //
+                    v = buf.trntotals[i]-s.dsrval[i];
+                    rep.rmserror = rep.rmserror+math.sqr(v);
+                    rep.avgerror = rep.avgerror+Math.Abs(v);
+                    if( (double)(s.dsrval[i])!=(double)(0) )
+                    {
+                        rep.avgrelerror = rep.avgrelerror+Math.Abs(v/s.dsrval[i]);
+                        avgrelcnt = avgrelcnt+1;
+                    }
+                    v = buf.oobtotals[i]-s.dsrval[i];
+                    rep.oobrmserror = rep.oobrmserror+math.sqr(v);
+                    rep.oobavgerror = rep.oobavgerror+Math.Abs(v);
+                    if( (double)(s.dsrval[i])!=(double)(0) )
+                    {
+                        rep.oobavgrelerror = rep.oobavgrelerror+Math.Abs(v/s.dsrval[i]);
+                        oobavgrelcnt = oobavgrelcnt+1;
+                    }
+                }
+            }
+            rep.relclserror = rep.relclserror/npoints;
+            rep.rmserror = Math.Sqrt(rep.rmserror/(npoints*nclasses));
+            rep.avgerror = rep.avgerror/(npoints*nclasses);
+            rep.avgrelerror = rep.avgrelerror/apserv.coalesce(avgrelcnt, 1, _params);
+            rep.oobrelclserror = rep.oobrelclserror/npoints;
+            rep.oobrmserror = Math.Sqrt(rep.oobrmserror/(npoints*nclasses));
+            rep.oobavgerror = rep.oobavgerror/(npoints*nclasses);
+            rep.oobavgrelerror = rep.oobavgrelerror/apserv.coalesce(oobavgrelcnt, 1, _params);
+        }
+
+
+        /*************************************************************************
+        Classification error
+        *************************************************************************/
+        private static int dfclserror(decisionforest df,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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( x[(int)Math.Round(df.trees[k])]<df.trees[k+1] )
+                {
+                    k = k+innernodewidth;
+                }
+                else
+                {
+                    k = offs+(int)Math.Round(df.trees[k+2]);
+                }
+            }
+        }
+
+
+    }
+    public class knn
+    {
+        /*************************************************************************
+        Buffer object which is used to perform  various  requests  (usually  model
+        inference) in the multithreaded mode (multiple threads working  with  same
+        KNN object).
+
+        This object should be created with KNNCreateBuffer().
+        *************************************************************************/
+        public class knnbuffer : apobject
+        {
+            public nearestneighbor.kdtreerequestbuffer treebuf;
+            public double[] x;
+            public double[] y;
+            public int[] tags;
+            public double[,] xy;
+            public knnbuffer()
+            {
+                init();
+            }
+            public override void init()
+            {
+                treebuf = new nearestneighbor.kdtreerequestbuffer();
+                x = new double[0];
+                y = new double[0];
+                tags = new int[0];
+                xy = new double[0,0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                knnbuffer _result = new knnbuffer();
+                _result.treebuf = (nearestneighbor.kdtreerequestbuffer)treebuf.make_copy();
+                _result.x = (double[])x.Clone();
+                _result.y = (double[])y.Clone();
+                _result.tags = (int[])tags.Clone();
+                _result.xy = (double[,])xy.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        A KNN builder object; this object encapsulates  dataset  and  all  related
+        settings, it is used to create an actual instance of KNN model.
+        *************************************************************************/
+        public class knnbuilder : apobject
+        {
+            public int dstype;
+            public int npoints;
+            public int nvars;
+            public bool iscls;
+            public int nout;
+            public double[,] dsdata;
+            public double[] dsrval;
+            public int[] dsival;
+            public int knnnrm;
+            public knnbuilder()
+            {
+                init();
+            }
+            public override void init()
+            {
+                dsdata = new double[0,0];
+                dsrval = new double[0];
+                dsival = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                knnbuilder _result = new knnbuilder();
+                _result.dstype = dstype;
+                _result.npoints = npoints;
+                _result.nvars = nvars;
+                _result.iscls = iscls;
+                _result.nout = nout;
+                _result.dsdata = (double[,])dsdata.Clone();
+                _result.dsrval = (double[])dsrval.Clone();
+                _result.dsival = (int[])dsival.Clone();
+                _result.knnnrm = knnnrm;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        KNN model, can be used for classification or regression
+        *************************************************************************/
+        public class knnmodel : apobject
+        {
+            public int nvars;
+            public int nout;
+            public int k;
+            public double eps;
+            public bool iscls;
+            public bool isdummy;
+            public nearestneighbor.kdtree tree;
+            public knnbuffer buffer;
+            public knnmodel()
+            {
+                init();
+            }
+            public override void init()
+            {
+                tree = new nearestneighbor.kdtree();
+                buffer = new knnbuffer();
+            }
+            public override alglib.apobject make_copy()
+            {
+                knnmodel _result = new knnmodel();
+                _result.nvars = nvars;
+                _result.nout = nout;
+                _result.k = k;
+                _result.eps = eps;
+                _result.iscls = iscls;
+                _result.isdummy = isdummy;
+                _result.tree = (nearestneighbor.kdtree)tree.make_copy();
+                _result.buffer = (knnbuffer)buffer.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        KNN training report.
+
+        Following fields store training set errors:
+        * relclserror       -   fraction of misclassified cases, [0,1]
+        * avgce             -   average cross-entropy in bits per symbol
+        * rmserror          -   root-mean-square error
+        * avgerror          -   average error
+        * avgrelerror       -   average relative error
+
+        For classification problems:
+        * RMS, AVG and AVGREL errors are calculated for posterior probabilities
+
+        For regression problems:
+        * RELCLS and AVGCE errors are zero
+        *************************************************************************/
+        public class knnreport : apobject
+        {
+            public double relclserror;
+            public double avgce;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public knnreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                knnreport _result = new knnreport();
+                _result.relclserror = relclserror;
+                _result.avgce = avgce;
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.avgrelerror = avgrelerror;
+                return _result;
+            }
+        };
+
+
+
+
+        public const int knnfirstversion = 0;
+
+
+        /*************************************************************************
+        This function creates buffer  structure  which  can  be  used  to  perform
+        parallel KNN requests.
+
+        KNN subpackage provides two sets of computing functions - ones  which  use
+        internal buffer of KNN model (these  functions are single-threaded because
+        they use same buffer, which can not  shared  between  threads),  and  ones
+        which use external buffer.
+
+        This function is used to initialize external buffer.
+
+        INPUT PARAMETERS
+            Model       -   KNN model which is associated with newly created buffer
+
+        OUTPUT PARAMETERS
+            Buf         -   external buffer.
+            
+            
+        IMPORTANT: buffer object should be used only with model which was used  to
+                   initialize buffer. Any attempt to  use  buffer  with  different
+                   object is dangerous - you  may   get  integrity  check  failure
+                   (exception) because sizes of internal  arrays  do  not  fit  to
+                   dimensions of the model structure.
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knncreatebuffer(knnmodel model,
+            knnbuffer buf,
+            alglib.xparams _params)
+        {
+            if( !model.isdummy )
+            {
+                nearestneighbor.kdtreecreaterequestbuffer(model.tree, buf.treebuf, _params);
+            }
+            buf.x = new double[model.nvars];
+            buf.y = new double[model.nout];
+        }
+
+
+        /*************************************************************************
+        This subroutine creates KNNBuilder object which is used to train KNN models.
+
+        By default, new builder stores empty dataset and some  reasonable  default
+        settings. At the very least, you should specify dataset prior to  building
+        KNN model. You can also tweak settings of the model construction algorithm
+        (recommended, although default settings should work well).
+
+        Following actions are mandatory:
+        * calling knnbuildersetdataset() to specify dataset
+        * calling knnbuilderbuildknnmodel() to build KNN model using current
+          dataset and default settings
+          
+        Additionally, you may call:
+        * knnbuildersetnorm() to change norm being used
+
+        INPUT PARAMETERS:
+            none
+
+        OUTPUT PARAMETERS:
+            S           -   KNN builder
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knnbuildercreate(knnbuilder s,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Empty dataset
+            //
+            s.dstype = -1;
+            s.npoints = 0;
+            s.nvars = 0;
+            s.iscls = false;
+            s.nout = 1;
+            
+            //
+            // Default training settings
+            //
+            s.knnnrm = 2;
+        }
+
+
+        /*************************************************************************
+        Specifies regression problem (one or more continuous  output variables are
+        predicted). There also exists "classification" version of this function.
+
+        This subroutine adds dense dataset to the internal storage of the  builder
+        object. Specifying your dataset in the dense format means that  the  dense
+        version of the KNN construction algorithm will be invoked.
+
+        INPUT PARAMETERS:
+            S           -   KNN builder object
+            XY          -   array[NPoints,NVars+NOut] (note: actual  size  can  be
+                            larger, only leading part is used anyway), dataset:
+                            * first NVars elements of each row store values of the
+                              independent variables
+                            * next NOut elements store  values  of  the  dependent
+                              variables
+            NPoints     -   number of rows in the dataset, NPoints>=1
+            NVars       -   number of independent variables, NVars>=1 
+            NOut        -   number of dependent variables, NOut>=1
+
+        OUTPUT PARAMETERS:
+            S           -   KNN builder
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knnbuildersetdatasetreg(knnbuilder s,
+            double[,] xy,
+            int npoints,
+            int nvars,
+            int nout,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            
+            //
+            // Check parameters
+            //
+            alglib.ap.assert(npoints>=1, "knnbuildersetdatasetreg: npoints<1");
+            alglib.ap.assert(nvars>=1, "knnbuildersetdatasetreg: nvars<1");
+            alglib.ap.assert(nout>=1, "knnbuildersetdatasetreg: nout<1");
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "knnbuildersetdatasetreg: rows(xy)<npoints");
+            alglib.ap.assert(alglib.ap.cols(xy)>=nvars+nout, "knnbuildersetdatasetreg: cols(xy)<nvars+nout");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, npoints, nvars+nout, _params), "knnbuildersetdatasetreg: xy parameter contains INFs or NANs");
+            
+            //
+            // Set dataset
+            //
+            s.dstype = 0;
+            s.iscls = false;
+            s.npoints = npoints;
+            s.nvars = nvars;
+            s.nout = nout;
+            apserv.rmatrixsetlengthatleast(ref s.dsdata, npoints, nvars, _params);
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=nvars-1; j++)
+                {
+                    s.dsdata[i,j] = xy[i,j];
+                }
+            }
+            apserv.rvectorsetlengthatleast(ref s.dsrval, npoints*nout, _params);
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=nout-1; j++)
+                {
+                    s.dsrval[i*nout+j] = xy[i,nvars+j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Specifies classification problem (two  or  more  classes  are  predicted).
+        There also exists "regression" version of this function.
+
+        This subroutine adds dense dataset to the internal storage of the  builder
+        object. Specifying your dataset in the dense format means that  the  dense
+        version of the KNN construction algorithm will be invoked.
+
+        INPUT PARAMETERS:
+            S           -   KNN builder object
+            XY          -   array[NPoints,NVars+1] (note:   actual   size  can  be
+                            larger, only leading part is used anyway), dataset:
+                            * first NVars elements of each row store values of the
+                              independent variables
+                            * next element stores class index, in [0,NClasses)
+            NPoints     -   number of rows in the dataset, NPoints>=1
+            NVars       -   number of independent variables, NVars>=1 
+            NClasses    -   number of classes, NClasses>=2
+
+        OUTPUT PARAMETERS:
+            S           -   KNN builder
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knnbuildersetdatasetcls(knnbuilder s,
+            double[,] xy,
+            int npoints,
+            int nvars,
+            int nclasses,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            
+            //
+            // Check parameters
+            //
+            alglib.ap.assert(npoints>=1, "knnbuildersetdatasetcls: npoints<1");
+            alglib.ap.assert(nvars>=1, "knnbuildersetdatasetcls: nvars<1");
+            alglib.ap.assert(nclasses>=2, "knnbuildersetdatasetcls: nclasses<2");
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "knnbuildersetdatasetcls: rows(xy)<npoints");
+            alglib.ap.assert(alglib.ap.cols(xy)>=nvars+1, "knnbuildersetdatasetcls: cols(xy)<nvars+1");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, npoints, nvars+1, _params), "knnbuildersetdatasetcls: xy parameter contains INFs or NANs");
+            for(i=0; i<=npoints-1; i++)
+            {
+                j = (int)Math.Round(xy[i,nvars]);
+                alglib.ap.assert(j>=0 && j<nclasses, "knnbuildersetdatasetcls: last column of xy contains invalid class number");
+            }
+            
+            //
+            // Set dataset
+            //
+            s.iscls = true;
+            s.dstype = 0;
+            s.npoints = npoints;
+            s.nvars = nvars;
+            s.nout = nclasses;
+            apserv.rmatrixsetlengthatleast(ref s.dsdata, npoints, nvars, _params);
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=nvars-1; j++)
+                {
+                    s.dsdata[i,j] = xy[i,j];
+                }
+            }
+            apserv.ivectorsetlengthatleast(ref s.dsival, npoints, _params);
+            for(i=0; i<=npoints-1; i++)
+            {
+                s.dsival[i] = (int)Math.Round(xy[i,nvars]);
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets norm type used for neighbor search.
+
+        INPUT PARAMETERS:
+            S           -   decision forest builder object
+            NormType    -   norm type:
+                            * 0      inf-norm
+                            * 1      1-norm
+                            * 2      Euclidean norm (default)
+
+        OUTPUT PARAMETERS:
+            S           -   decision forest builder
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knnbuildersetnorm(knnbuilder s,
+            int nrmtype,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert((nrmtype==0 || nrmtype==1) || nrmtype==2, "knnbuildersetnorm: unexpected norm type");
+            s.knnnrm = nrmtype;
+        }
+
+
+        /*************************************************************************
+        This subroutine builds KNN model  according  to  current  settings,  using
+        dataset internally stored in the builder object.
+
+        The model being built performs inference using Eps-approximate  K  nearest
+        neighbors search algorithm, with:
+        * K=1,  Eps=0 corresponding to the "nearest neighbor algorithm"
+        * K>1,  Eps=0 corresponding to the "K nearest neighbors algorithm"
+        * K>=1, Eps>0 corresponding to "approximate nearest neighbors algorithm"
+
+        An approximate KNN is a good option for high-dimensional  datasets  (exact
+        KNN works slowly when dimensions count grows).
+
+        An ALGLIB implementation of kd-trees is used to perform k-nn searches.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            S       -   KNN builder object
+            K       -   number of neighbors to search for, K>=1
+            Eps     -   approximation factor:
+                        * Eps=0 means that exact kNN search is performed
+                        * Eps>0 means that (1+Eps)-approximate search is performed
+
+        OUTPUT PARAMETERS:
+            Model       -   KNN model
+            Rep         -   report
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knnbuilderbuildknnmodel(knnbuilder s,
+            int k,
+            double eps,
+            knnmodel model,
+            knnreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int nvars = 0;
+            int nout = 0;
+            int npoints = 0;
+            bool iscls = new bool();
+            double[,] xy = new double[0,0];
+            int[] tags = new int[0];
+
+            npoints = s.npoints;
+            nvars = s.nvars;
+            nout = s.nout;
+            iscls = s.iscls;
+            
+            //
+            // Check settings
+            //
+            alglib.ap.assert(k>=1, "knnbuilderbuildknnmodel: k<1");
+            alglib.ap.assert(math.isfinite(eps) && (double)(eps)>=(double)(0), "knnbuilderbuildknnmodel: eps<0");
+            
+            //
+            // Prepare output
+            //
+            clearreport(rep, _params);
+            model.nvars = nvars;
+            model.nout = nout;
+            model.iscls = iscls;
+            model.k = k;
+            model.eps = eps;
+            model.isdummy = false;
+            
+            //
+            // Quick exit for empty dataset
+            //
+            if( s.dstype==-1 )
+            {
+                model.isdummy = true;
+                return;
+            }
+            
+            //
+            // Build kd-tree
+            //
+            if( iscls )
+            {
+                xy = new double[npoints, nvars+1];
+                tags = new int[npoints];
+                for(i=0; i<=npoints-1; i++)
+                {
+                    for(j=0; j<=nvars-1; j++)
+                    {
+                        xy[i,j] = s.dsdata[i,j];
+                    }
+                    xy[i,nvars] = s.dsival[i];
+                    tags[i] = s.dsival[i];
+                }
+                nearestneighbor.kdtreebuildtagged(xy, tags, npoints, nvars, 0, s.knnnrm, model.tree, _params);
+            }
+            else
+            {
+                xy = new double[npoints, nvars+nout];
+                for(i=0; i<=npoints-1; i++)
+                {
+                    for(j=0; j<=nvars-1; j++)
+                    {
+                        xy[i,j] = s.dsdata[i,j];
+                    }
+                    for(j=0; j<=nout-1; j++)
+                    {
+                        xy[i,nvars+j] = s.dsrval[i*nout+j];
+                    }
+                }
+                nearestneighbor.kdtreebuild(xy, npoints, nvars, nout, s.knnnrm, model.tree, _params);
+            }
+            
+            //
+            // Build buffer
+            //
+            knncreatebuffer(model, model.buffer, _params);
+            
+            //
+            // Report
+            //
+            knnallerrors(model, xy, npoints, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Changing search settings of KNN model.
+
+        K and EPS parameters of KNN  (AKNN)  search  are  specified  during  model
+        construction. However, plain KNN algorithm with Euclidean distance  allows
+        you to change them at any moment.
+
+        NOTE: future versions of KNN model may support advanced versions  of  KNN,
+              such as NCA or LMNN. It is possible that such algorithms won't allow
+              you to change search settings on the fly. If you call this  function
+              for an algorithm which does not support on-the-fly changes, it  will
+              throw an exception.
+
+        INPUT PARAMETERS:
+            Model   -   KNN model
+            K       -   K>=1, neighbors count
+            EPS     -   accuracy of the EPS-approximate NN search. Set to 0.0,  if
+                        you want to perform "classic" KNN search.  Specify  larger
+                        values  if  you  need  to  speed-up  high-dimensional  KNN
+                        queries.
+
+        OUTPUT PARAMETERS:
+            nothing on success, exception on failure
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knnrewritekeps(knnmodel model,
+            int k,
+            double eps,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(k>=1, "knnrewritekeps: k<1");
+            alglib.ap.assert(math.isfinite(eps) && (double)(eps)>=(double)(0), "knnrewritekeps: eps<0");
+            model.k = k;
+            model.eps = eps;
+        }
+
+
+        /*************************************************************************
+        Inference using KNN model.
+
+        See also knnprocess0(), knnprocessi() and knnclassify() for options with a
+        bit more convenient interface.
+
+        IMPORTANT: this function is thread-unsafe and modifies internal structures
+                   of the model! You can not use same model  object  for  parallel
+                   evaluation from several threads.
+                   
+                   Use knntsprocess() with independent  thread-local  buffers,  if
+                   you need thread-safe evaluation.
+
+        INPUT PARAMETERS:
+            Model   -   KNN model
+            X       -   input vector,  array[0..NVars-1].
+            Y       -   possible preallocated buffer. Reused if long enough.
+
+        OUTPUT PARAMETERS:
+            Y       -   result. Regression estimate when solving regression  task,
+                        vector of posterior probabilities for classification task.
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knnprocess(knnmodel model,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            knntsprocess(model, model.buffer, x, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        This function returns first component of the  inferred  vector  (i.e.  one
+        with index #0).
+
+        It is a convenience wrapper for knnprocess() intended for either:
+        * 1-dimensional regression problems
+        * 2-class classification problems
+
+        In the former case this function returns inference result as scalar, which
+        is definitely more convenient that wrapping it as vector.  In  the  latter
+        case it returns probability of object belonging to class #0.
+
+        If you call it for anything different from two cases above, it  will  work
+        as defined, i.e. return y[0], although it is of less use in such cases.
+
+        IMPORTANT: this function is thread-unsafe and modifies internal structures
+                   of the model! You can not use same model  object  for  parallel
+                   evaluation from several threads.
+                   
+                   Use knntsprocess() with independent  thread-local  buffers,  if
+                   you need thread-safe evaluation.
+
+        INPUT PARAMETERS:
+            Model   -   KNN model
+            X       -   input vector,  array[0..NVars-1].
+
+        RESULT:
+            Y[0]
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static double knnprocess0(knnmodel model,
+            double[] x,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int i = 0;
+            int nvars = 0;
+
+            nvars = model.nvars;
+            for(i=0; i<=nvars-1; i++)
+            {
+                model.buffer.x[i] = x[i];
+            }
+            processinternal(model, model.buffer, _params);
+            result = model.buffer.y[0];
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns most probable class number for an  input  X.  It  is
+        same as calling knnprocess(model,x,y), then determining i=argmax(y[i]) and
+        returning i.
+
+        A class number in [0,NOut) range in returned for classification  problems,
+        -1 is returned when this function is called for regression problems.
+
+        IMPORTANT: this function is thread-unsafe and modifies internal structures
+                   of the model! You can not use same model  object  for  parallel
+                   evaluation from several threads.
+                   
+                   Use knntsprocess() with independent  thread-local  buffers,  if
+                   you need thread-safe evaluation.
+
+        INPUT PARAMETERS:
+            Model   -   KNN model
+            X       -   input vector,  array[0..NVars-1].
+
+        RESULT:
+            class number, -1 for regression tasks
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static int knnclassify(knnmodel model,
+            double[] x,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+            int nvars = 0;
+            int nout = 0;
+
+            if( !model.iscls )
+            {
+                result = -1;
+                return result;
+            }
+            nvars = model.nvars;
+            nout = model.nout;
+            for(i=0; i<=nvars-1; i++)
+            {
+                model.buffer.x[i] = x[i];
+            }
+            processinternal(model, model.buffer, _params);
+            result = 0;
+            for(i=1; i<=nout-1; i++)
+            {
+                if( model.buffer.y[i]>model.buffer.y[result] )
+                {
+                    result = i;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        'interactive' variant of knnprocess()  for  languages  like  Python  which
+        support constructs like "y = knnprocessi(model,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.
+
+        IMPORTANT: this  function  is  thread-unsafe  and  may   modify   internal
+                   structures of the model! You can not use same model  object for
+                   parallel evaluation from several threads.
+                   
+                   Use knntsprocess()  with  independent  thread-local  buffers if
+                   you need thread-safe evaluation.
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knnprocessi(knnmodel model,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            y = new double[0];
+
+            knnprocess(model, x, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        Thread-safe procesing using external buffer for temporaries.
+
+        This function is thread-safe (i.e .  you  can  use  same  KNN  model  from
+        multiple threads) as long as you use different buffer objects for different
+        threads.
+
+        INPUT PARAMETERS:
+            Model   -   KNN model
+            Buf     -   buffer object, must be  allocated  specifically  for  this
+                        model with knncreatebuffer().
+            X       -   input vector,  array[NVars]
+
+        OUTPUT PARAMETERS:
+            Y       -   result, array[NOut].   Regression  estimate  when  solving
+                        regression task,  vector  of  posterior  probabilities for
+                        a classification task.
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knntsprocess(knnmodel model,
+            knnbuffer buf,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int nvars = 0;
+            int nout = 0;
+
+            nvars = model.nvars;
+            nout = model.nout;
+            for(i=0; i<=nvars-1; i++)
+            {
+                buf.x[i] = x[i];
+            }
+            processinternal(model, buf, _params);
+            if( alglib.ap.len(y)<nout )
+            {
+                y = new double[nout];
+            }
+            for(i=0; i<=nout-1; i++)
+            {
+                y[i] = buf.y[i];
+            }
+        }
+
+
+        /*************************************************************************
+        Relative classification error on the test set
+
+        INPUT PARAMETERS:
+            Model   -   KNN model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            percent of incorrectly classified cases.
+            Zero if model solves regression task.
+            
+        NOTE: if  you  need several different kinds of error metrics, it is better
+              to use knnallerrors() which computes all error metric  with just one
+              pass over dataset. 
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static double knnrelclserror(knnmodel model,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            knnreport rep = new knnreport();
+
+            knnallerrors(model, xy, npoints, rep, _params);
+            result = rep.relclserror;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average cross-entropy (in bits per element) on the test set
+
+        INPUT PARAMETERS:
+            Model   -   KNN model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            CrossEntropy/NPoints.
+            Zero if model solves regression task.
+
+        NOTE: the cross-entropy metric is too unstable when used to  evaluate  KNN
+              models (such models can report exactly  zero probabilities),  so  we
+              do not recommend using it.
+            
+        NOTE: if  you  need several different kinds of error metrics, it is better
+              to use knnallerrors() which computes all error metric  with just one
+              pass over dataset. 
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static double knnavgce(knnmodel model,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            knnreport rep = new knnreport();
+
+            knnallerrors(model, xy, npoints, rep, _params);
+            result = rep.avgce;
+            return result;
+        }
+
+
+        /*************************************************************************
+        RMS error on the test set.
+
+        Its meaning for regression task is obvious. As for classification problems,
+        RMS error means error when estimating posterior probabilities.
+
+        INPUT PARAMETERS:
+            Model   -   KNN model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            root mean square error.
+            
+        NOTE: if  you  need several different kinds of error metrics, it is better
+              to use knnallerrors() which computes all error metric  with just one
+              pass over dataset.
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static double knnrmserror(knnmodel model,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            knnreport rep = new knnreport();
+
+            knnallerrors(model, xy, npoints, rep, _params);
+            result = rep.rmserror;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average error on the test set
+
+        Its meaning for regression task is obvious. As for classification problems,
+        average error means error when estimating posterior probabilities.
+
+        INPUT PARAMETERS:
+            Model   -   KNN model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            average error
+            
+        NOTE: if  you  need several different kinds of error metrics, it is better
+              to use knnallerrors() which computes all error metric  with just one
+              pass over dataset.
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static double knnavgerror(knnmodel model,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            knnreport rep = new knnreport();
+
+            knnallerrors(model, xy, npoints, rep, _params);
+            result = rep.avgerror;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Average relative error on the test set
+
+        Its meaning for regression task is obvious. As for classification problems,
+        average relative error means error when estimating posterior probabilities.
+
+        INPUT PARAMETERS:
+            Model   -   KNN model
+            XY      -   test set
+            NPoints -   test set size
+
+        RESULT:
+            average relative error
+            
+        NOTE: if  you  need several different kinds of error metrics, it is better
+              to use knnallerrors() which computes all error metric  with just one
+              pass over dataset.
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static double knnavgrelerror(knnmodel model,
+            double[,] xy,
+            int npoints,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            knnreport rep = new knnreport();
+
+            knnallerrors(model, xy, npoints, rep, _params);
+            result = rep.avgrelerror;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Calculates all kinds of errors for the model in one call.
+
+        INPUT PARAMETERS:
+            Model   -   KNN model
+            XY      -   test set:
+                        * one row per point
+                        * first NVars columns store independent variables
+                        * depending on problem type:
+                          * next column stores class number in [0,NClasses) -  for
+                            classification problems
+                          * next NOut columns  store  dependent  variables  -  for
+                            regression problems
+            NPoints -   test set size, NPoints>=0
+
+        OUTPUT PARAMETERS:
+            Rep     -   following fields are loaded with errors for both regression
+                        and classification models:
+                        * rep.rmserror - RMS error for the output
+                        * rep.avgerror - average error
+                        * rep.avgrelerror - average relative error
+                        following fields are set only  for classification  models,
+                        zero for regression ones:
+                        * relclserror   - relative classification error, in [0,1]
+                        * avgce - average cross-entropy in bits per dataset entry
+
+        NOTE: the cross-entropy metric is too unstable when used to  evaluate  KNN
+              models (such models can report exactly  zero probabilities),  so  we
+              do not recommend using it.
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knnallerrors(knnmodel model,
+            double[,] xy,
+            int npoints,
+            knnreport rep,
+            alglib.xparams _params)
+        {
+            knnbuffer buf = new knnbuffer();
+            double[] desiredy = new double[0];
+            double[] errbuf = new double[0];
+            int nvars = 0;
+            int nout = 0;
+            int ny = 0;
+            bool iscls = new bool();
+            int i = 0;
+            int j = 0;
+
+            nvars = model.nvars;
+            nout = model.nout;
+            iscls = model.iscls;
+            if( iscls )
+            {
+                ny = 1;
+            }
+            else
+            {
+                ny = nout;
+            }
+            
+            //
+            // Check input
+            //
+            alglib.ap.assert(npoints>=0, "knnallerrors: npoints<0");
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "knnallerrors: rows(xy)<npoints");
+            alglib.ap.assert(alglib.ap.cols(xy)>=nvars+ny, "knnallerrors: cols(xy)<nvars+nout");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, npoints, nvars+ny, _params), "knnallerrors: xy parameter contains INFs or NANs");
+            
+            //
+            // Clean up report
+            //
+            clearreport(rep, _params);
+            
+            //
+            // Quick exit if needed
+            //
+            if( model.isdummy || npoints==0 )
+            {
+                return;
+            }
+            
+            //
+            // Process using local buffer
+            //
+            knncreatebuffer(model, buf, _params);
+            if( iscls )
+            {
+                bdss.dserrallocate(nout, ref errbuf, _params);
+            }
+            else
+            {
+                bdss.dserrallocate(-nout, ref errbuf, _params);
+            }
+            desiredy = new double[ny];
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=nvars-1; j++)
+                {
+                    buf.x[j] = xy[i,j];
+                }
+                if( iscls )
+                {
+                    j = (int)Math.Round(xy[i,nvars]);
+                    alglib.ap.assert(j>=0 && j<nout, "knnallerrors: one of the class labels is not in [0,NClasses)");
+                    desiredy[0] = j;
+                }
+                else
+                {
+                    for(j=0; j<=nout-1; j++)
+                    {
+                        desiredy[j] = xy[i,nvars+j];
+                    }
+                }
+                processinternal(model, buf, _params);
+                bdss.dserraccumulate(ref errbuf, buf.y, desiredy, _params);
+            }
+            bdss.dserrfinish(ref errbuf, _params);
+            
+            //
+            // Extract results
+            //
+            if( iscls )
+            {
+                rep.relclserror = errbuf[0];
+                rep.avgce = errbuf[1];
+            }
+            rep.rmserror = errbuf[2];
+            rep.avgerror = errbuf[3];
+            rep.avgrelerror = errbuf[4];
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knnalloc(alglib.serializer s,
+            knnmodel model,
+            alglib.xparams _params)
+        {
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            if( !model.isdummy )
+            {
+                nearestneighbor.kdtreealloc(s, model.tree, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knnserialize(alglib.serializer s,
+            knnmodel model,
+            alglib.xparams _params)
+        {
+            s.serialize_int(scodes.getknnserializationcode(_params));
+            s.serialize_int(knnfirstversion);
+            s.serialize_int(model.nvars);
+            s.serialize_int(model.nout);
+            s.serialize_int(model.k);
+            s.serialize_double(model.eps);
+            s.serialize_bool(model.iscls);
+            s.serialize_bool(model.isdummy);
+            if( !model.isdummy )
+            {
+                nearestneighbor.kdtreeserialize(s, model.tree, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        public static void knnunserialize(alglib.serializer s,
+            knnmodel model,
+            alglib.xparams _params)
+        {
+            int i0 = 0;
+            int i1 = 0;
+
+            
+            //
+            // check correctness of header
+            //
+            i0 = s.unserialize_int();
+            alglib.ap.assert(i0==scodes.getknnserializationcode(_params), "KNNUnserialize: stream header corrupted");
+            i1 = s.unserialize_int();
+            alglib.ap.assert(i1==knnfirstversion, "KNNUnserialize: stream header corrupted");
+            
+            //
+            // Unserialize data
+            //
+            model.nvars = s.unserialize_int();
+            model.nout = s.unserialize_int();
+            model.k = s.unserialize_int();
+            model.eps = s.unserialize_double();
+            model.iscls = s.unserialize_bool();
+            model.isdummy = s.unserialize_bool();
+            if( !model.isdummy )
+            {
+                nearestneighbor.kdtreeunserialize(s, model.tree, _params);
+            }
+            
+            //
+            // Prepare local buffer
+            //
+            knncreatebuffer(model, model.buffer, _params);
+        }
+
+
+        /*************************************************************************
+        Sets report fields to their default values
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        private static void clearreport(knnreport rep,
+            alglib.xparams _params)
+        {
+            rep.relclserror = 0;
+            rep.avgce = 0;
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.avgrelerror = 0;
+        }
+
+
+        /*************************************************************************
+        This function processes buf.X and stores result to buf.Y
+
+        INPUT PARAMETERS
+            Model       -   KNN model
+            Buf         -   processing buffer.
+            
+            
+        IMPORTANT: buffer object should be used only with model which was used  to
+                   initialize buffer. Any attempt to  use  buffer  with  different
+                   object is dangerous - you  may   get  integrity  check  failure
+                   (exception) because sizes of internal  arrays  do  not  fit  to
+                   dimensions of the model structure.
+
+          -- ALGLIB --
+             Copyright 15.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        private static void processinternal(knnmodel model,
+            knnbuffer buf,
+            alglib.xparams _params)
+        {
+            int nvars = 0;
+            int nout = 0;
+            bool iscls = new bool();
+            int nncnt = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            nvars = model.nvars;
+            nout = model.nout;
+            iscls = model.iscls;
+            
+            //
+            // Quick exit if needed
+            //
+            if( model.isdummy )
+            {
+                for(i=0; i<=nout-1; i++)
+                {
+                    buf.y[i] = 0;
+                }
+                return;
+            }
+            
+            //
+            // Perform request, average results
+            //
+            for(i=0; i<=nout-1; i++)
+            {
+                buf.y[i] = 0;
+            }
+            nncnt = nearestneighbor.kdtreetsqueryaknn(model.tree, buf.treebuf, buf.x, model.k, true, model.eps, _params);
+            v = 1/apserv.coalesce(nncnt, 1, _params);
+            if( iscls )
+            {
+                nearestneighbor.kdtreetsqueryresultstags(model.tree, buf.treebuf, ref buf.tags, _params);
+                for(i=0; i<=nncnt-1; i++)
+                {
+                    j = buf.tags[i];
+                    buf.y[j] = buf.y[j]+v;
+                }
+            }
+            else
+            {
+                nearestneighbor.kdtreetsqueryresultsxy(model.tree, buf.treebuf, ref buf.xy, _params);
+                for(i=0; i<=nncnt-1; i++)
+                {
+                    for(j=0; j<=nout-1; j++)
+                    {
+                        buf.y[j] = buf.y[j]+v*buf.xy[i,nvars+j];
+                    }
+                }
+            }
+        }
+
+
+    }
+    public class datacomp
+    {
+        /*************************************************************************
+        k-means++ clusterization.
+        Backward compatibility function, we recommend to use CLUSTERING subpackage
+        as better replacement.
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            double[,] dummy = new double[0,0];
+            int itscnt = 0;
+            double e = 0;
+            clustering.kmeansbuffers buf = new clustering.kmeansbuffers();
+
+            info = 0;
+            c = new double[0,0];
+            xyc = new int[0];
+
+            clustering.kmeansinitbuf(buf, _params);
+            clustering.kmeansgenerateinternal(xy, npoints, nvars, k, 0, 1, 0, restarts, false, ref info, ref itscnt, ref c, true, ref dummy, false, ref xyc, ref e, buf, _params);
+        }
+
+
+    }
+}
+
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/diffequations.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/diffequations.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/diffequations.cs	(revision 17158)
@@ -0,0 +1,1047 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 164
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class odesolverstate : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new odesolverstate((odesolver.odesolverstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new odesolverreport((odesolver.odesolverreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void odesolverrkck(double[] y, int n, double[] x, int m, double eps, double h, out odesolverstate state, alglib.xparams _params)
+    {
+        state = new odesolverstate();
+        odesolver.odesolverrkck(y, n, x, m, eps, h, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void odesolverrkck(double[] y, double[] x, double eps, double h, out odesolverstate state, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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)
+    {
+    
+        return odesolver.odesolveriteration(state.innerobj, null);
+    }
+    
+    public static bool odesolveriteration(odesolverstate state, alglib.xparams _params)
+    {
+    
+        return odesolver.odesolveriteration(state.innerobj, _params);
+    }
+    /*************************************************************************
+    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)
+    {
+        odesolversolve(state, diff, obj, null);
+    }
+    
+    public static void odesolversolve(odesolverstate state, ndimensional_ode_rp diff, object obj, alglib.xparams _params)
+    {
+        if( diff==null )
+            throw new alglibexception("ALGLIB: error in 'odesolversolve()' (diff is null)");
+        while( alglib.odesolveriteration(state, _params) )
+        {
+            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, null);
+    }
+    
+    public static void odesolverresults(odesolverstate state, out int m, out double[] xtbl, out double[,] ytbl, out odesolverreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    }
+
+}
+public partial class alglib
+{
+    public class odesolver
+    {
+        public class odesolverstate : apobject
+        {
+            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()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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 override alglib.apobject make_copy()
+            {
+                odesolverstate _result = new odesolverstate();
+                _result.n = n;
+                _result.m = m;
+                _result.xscale = xscale;
+                _result.h = h;
+                _result.eps = eps;
+                _result.fraceps = fraceps;
+                _result.yc = (double[])yc.Clone();
+                _result.escale = (double[])escale.Clone();
+                _result.xg = (double[])xg.Clone();
+                _result.solvertype = solvertype;
+                _result.needdy = needdy;
+                _result.x = x;
+                _result.y = (double[])y.Clone();
+                _result.dy = (double[])dy.Clone();
+                _result.ytbl = (double[,])ytbl.Clone();
+                _result.repterminationtype = repterminationtype;
+                _result.repnfev = repnfev;
+                _result.yn = (double[])yn.Clone();
+                _result.yns = (double[])yns.Clone();
+                _result.rka = (double[])rka.Clone();
+                _result.rkc = (double[])rkc.Clone();
+                _result.rkcs = (double[])rkcs.Clone();
+                _result.rkb = (double[,])rkb.Clone();
+                _result.rkk = (double[,])rkk.Clone();
+                _result.rstate = (rcommstate)rstate.make_copy();
+                return _result;
+            }
+        };
+
+
+        public class odesolverreport : apobject
+        {
+            public int nfev;
+            public int terminationtype;
+            public odesolverreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                odesolverreport _result = new odesolverreport();
+                _result.nfev = nfev;
+                _result.terminationtype = terminationtype;
+                return _result;
+            }
+        };
+
+
+
+
+        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.xparams _params)
+        {
+            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, _params), "ODESolverRKCK: Y contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x, m, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+
+          -- ALGLIB --
+             Copyright 01.09.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool odesolveriteration(odesolverstate state,
+            alglib.xparams _params)
+        {
+            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 = 359;
+                m = -58;
+                i = -919;
+                j = -909;
+                k = 81;
+                klimit = 255;
+                gridpoint = false;
+                xc = -788;
+                v = 809;
+                h = 205;
+                h2 = -838;
+                err = 939;
+                maxgrowpow = -526;
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/fasttransforms.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/fasttransforms.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/fasttransforms.cs	(revision 17158)
@@ -0,0 +1,3450 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 164
+#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, null);
+    }
+    
+    public static void fftc1d(ref complex[] a, int n, alglib.xparams _params)
+    {
+    
+        fft.fftc1d(ref a, n, _params);
+    }
+            
+    public static void fftc1d(ref complex[] a)
+    {
+        int n;
+    
+    
+        n = ap.len(a);
+        fft.fftc1d(ref a, n, null);
+    
+        return;
+    }
+            
+    public static void fftc1d(ref complex[] a, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(a);
+        fft.fftc1d(ref a, n, _params);
+    
+        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, null);
+    }
+    
+    public static void fftc1dinv(ref complex[] a, int n, alglib.xparams _params)
+    {
+    
+        fft.fftc1dinv(ref a, n, _params);
+    }
+            
+    public static void fftc1dinv(ref complex[] a)
+    {
+        int n;
+    
+    
+        n = ap.len(a);
+        fft.fftc1dinv(ref a, n, null);
+    
+        return;
+    }
+            
+    public static void fftc1dinv(ref complex[] a, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(a);
+        fft.fftc1dinv(ref a, n, _params);
+    
+        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, null);
+    }
+    
+    public static void fftr1d(double[] a, int n, out complex[] f, alglib.xparams _params)
+    {
+        f = new complex[0];
+        fft.fftr1d(a, n, ref f, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void fftr1d(double[] a, out complex[] f, alglib.xparams _params)
+    {
+        int n;
+    
+        f = new complex[0];
+        n = ap.len(a);
+        fft.fftr1d(a, n, ref f, _params);
+    
+        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, null);
+    }
+    
+    public static void fftr1dinv(complex[] f, int n, out double[] a, alglib.xparams _params)
+    {
+        a = new double[0];
+        fft.fftr1dinv(f, n, ref a, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void fftr1dinv(complex[] f, out double[] a, alglib.xparams _params)
+    {
+        int n;
+    
+        a = new double[0];
+        n = ap.len(f);
+        fft.fftr1dinv(f, n, ref a, _params);
+    
+        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, null);
+    }
+    
+    public static void fhtr1d(ref double[] a, int n, alglib.xparams _params)
+    {
+    
+        fht.fhtr1d(ref a, n, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void fhtr1dinv(ref double[] a, int n, alglib.xparams _params)
+    {
+    
+        fht.fhtr1dinv(ref a, n, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void convc1d(complex[] a, int m, complex[] b, int n, out complex[] r, alglib.xparams _params)
+    {
+        r = new complex[0];
+        conv.convc1d(a, m, b, n, ref r, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void convc1dinv(complex[] a, int m, complex[] b, int n, out complex[] r, alglib.xparams _params)
+    {
+        r = new complex[0];
+        conv.convc1dinv(a, m, b, n, ref r, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void convc1dcircular(complex[] s, int m, complex[] r, int n, out complex[] c, alglib.xparams _params)
+    {
+        c = new complex[0];
+        conv.convc1dcircular(s, m, r, n, ref c, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void convc1dcircularinv(complex[] a, int m, complex[] b, int n, out complex[] r, alglib.xparams _params)
+    {
+        r = new complex[0];
+        conv.convc1dcircularinv(a, m, b, n, ref r, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void convr1d(double[] a, int m, double[] b, int n, out double[] r, alglib.xparams _params)
+    {
+        r = new double[0];
+        conv.convr1d(a, m, b, n, ref r, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void convr1dinv(double[] a, int m, double[] b, int n, out double[] r, alglib.xparams _params)
+    {
+        r = new double[0];
+        conv.convr1dinv(a, m, b, n, ref r, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void convr1dcircular(double[] s, int m, double[] r, int n, out double[] c, alglib.xparams _params)
+    {
+        c = new double[0];
+        conv.convr1dcircular(s, m, r, n, ref c, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void convr1dcircularinv(double[] a, int m, double[] b, int n, out double[] r, alglib.xparams _params)
+    {
+        r = new double[0];
+        conv.convr1dcircularinv(a, m, b, n, ref r, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void corrc1d(complex[] signal, int n, complex[] pattern, int m, out complex[] r, alglib.xparams _params)
+    {
+        r = new complex[0];
+        corr.corrc1d(signal, n, pattern, m, ref r, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void corrc1dcircular(complex[] signal, int m, complex[] pattern, int n, out complex[] c, alglib.xparams _params)
+    {
+        c = new complex[0];
+        corr.corrc1dcircular(signal, m, pattern, n, ref c, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void corrr1d(double[] signal, int n, double[] pattern, int m, out double[] r, alglib.xparams _params)
+    {
+        r = new double[0];
+        corr.corrr1d(signal, n, pattern, m, ref r, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void corrr1dcircular(double[] signal, int m, double[] pattern, int n, out double[] c, alglib.xparams _params)
+    {
+        c = new double[0];
+        corr.corrr1dcircular(signal, m, pattern, n, ref c, _params);
+    }
+
+}
+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,
+            alglib.xparams _params)
+        {
+            ftbase.fasttransformplan plan = new ftbase.fasttransformplan();
+            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, _params), "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.ftcomplexfftplan(n, 1, plan, _params);
+            ftbase.ftapplyplan(plan, buf, 0, 1, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n2 = 0;
+            int idx = 0;
+            complex hn = 0;
+            complex hmnc = 0;
+            complex v = 0;
+            double[] buf = new double[0];
+            ftbase.fasttransformplan plan = new ftbase.fasttransformplan();
+            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, _params), "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.ftcomplexfftplan(n2, 1, plan, _params);
+                ftbase.ftapplyplan(plan, buf, 0, 1, _params);
+                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, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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.fasttransformplan plan,
+            alglib.xparams _params)
+        {
+            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.ftapplyplan(plan, buf, 0, 1, _params);
+            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.fasttransformplan plan,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                a[i] = a[i]/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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                return;
+            }
+            convc1dx(a, m, b, n, false, -1, 0, ref r, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int p = 0;
+            double[] buf = new double[0];
+            double[] buf2 = new double[0];
+            ftbase.fasttransformplan plan = new ftbase.fasttransformplan();
+            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, _params);
+            ftbase.ftcomplexfftplan(p, 1, plan, _params);
+            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.ftapplyplan(plan, buf, 0, 1, _params);
+            ftbase.ftapplyplan(plan, buf2, 0, 1, _params);
+            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.ftapplyplan(plan, buf, 0, 1, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                return;
+            }
+            convc1dx(s, m, r, n, true, -1, 0, ref c, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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.fasttransformplan plan = new ftbase.fasttransformplan();
+            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, _params);
+                return;
+            }
+            
+            //
+            // Task is normalized
+            //
+            ftbase.ftcomplexfftplan(m, 1, plan, _params);
+            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.ftapplyplan(plan, buf, 0, 1, _params);
+            ftbase.ftapplyplan(plan, buf2, 0, 1, _params);
+            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.ftapplyplan(plan, buf, 0, 1, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                return;
+            }
+            convr1dx(a, m, b, n, false, -1, 0, ref r, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int p = 0;
+            double[] buf = new double[0];
+            double[] buf2 = new double[0];
+            double[] buf3 = new double[0];
+            ftbase.fasttransformplan plan = new ftbase.fasttransformplan();
+            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, _params);
+            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.ftcomplexfftplan(p/2, 1, plan, _params);
+            fft.fftr1dinternaleven(ref buf, p, ref buf3, plan, _params);
+            fft.fftr1dinternaleven(ref buf2, p, ref buf3, plan, _params);
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                return;
+            }
+            
+            //
+            // reduce to usual convolution
+            //
+            convr1dx(s, m, r, n, true, -1, 0, ref c, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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.fasttransformplan plan = new ftbase.fasttransformplan();
+            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, _params);
+                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.ftcomplexfftplan(m/2, 1, plan, _params);
+                fft.fftr1dinternaleven(ref buf, m, ref buf3, plan, _params);
+                fft.fftr1dinternaleven(ref buf2, m, ref buf3, plan, _params);
+                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, _params);
+                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, _params);
+                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, _params);
+                for(i=0; i<=(int)Math.Floor((double)m/(double)2); i++)
+                {
+                    cbuf[i] = cbuf[i]/cbuf2[i];
+                }
+                fft.fftr1dinv(cbuf, m, ref r, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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.fasttransformplan plan = new ftbase.fasttransformplan();
+            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, _params) )
+                    {
+                        
+                        //
+                        // special code for circular convolution of a sequence with a smooth length
+                        //
+                        flopcand = 3*ftbase.ftbasegetflopestimate(m, _params)+6*m;
+                        if( (double)(flopcand)<(double)(flopbest) )
+                        {
+                            algbest = 1;
+                            flopbest = flopcand;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // general cyclic/non-cyclic convolution
+                        //
+                        p = ftbase.ftbasefindsmooth(m+n-1, _params);
+                        flopcand = 3*ftbase.ftbasegetflopestimate(p, _params)+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, _params)+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, _params);
+                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, _params) )
+                {
+                    
+                    //
+                    // special code for circular convolution with smooth M
+                    //
+                    ftbase.ftcomplexfftplan(m, 1, plan, _params);
+                    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.ftapplyplan(plan, buf, 0, 1, _params);
+                    ftbase.ftapplyplan(plan, buf2, 0, 1, _params);
+                    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.ftapplyplan(plan, buf, 0, 1, _params);
+                    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, _params);
+                    ftbase.ftcomplexfftplan(p, 1, plan, _params);
+                    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.ftapplyplan(plan, buf, 0, 1, _params);
+                    ftbase.ftapplyplan(plan, buf2, 0, 1, _params);
+                    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.ftapplyplan(plan, buf, 0, 1, _params);
+                    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, _params);
+                
+                //
+                // prepare FFT plan for chunks of A
+                //
+                ftbase.ftcomplexfftplan(q+n-1, 1, plan, _params);
+                
+                //
+                // 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.ftapplyplan(plan, buf, 0, 1, _params);
+                    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.ftapplyplan(plan, buf, 0, 1, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            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.fasttransformplan plan = new ftbase.fasttransformplan();
+            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, _params)) && m%2==0 )
+                    {
+                        
+                        //
+                        // special code for circular convolution of a sequence with a smooth length
+                        //
+                        flopcand = 3*ftbase.ftbasegetflopestimate(m/2, _params)+(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, _params);
+                        flopcand = 3*ftbase.ftbasegetflopestimate(p/2, _params)+(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, _params)+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, _params);
+                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, _params)) && 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.ftcomplexfftplan(m/2, 1, plan, _params);
+                    fft.fftr1dinternaleven(ref buf, m, ref buf3, plan, _params);
+                    fft.fftr1dinternaleven(ref buf2, m, ref buf3, plan, _params);
+                    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, _params);
+                    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, _params);
+                    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.ftcomplexfftplan(p/2, 1, plan, _params);
+                    fft.fftr1dinternaleven(ref buf, p, ref buf3, plan, _params);
+                    fft.fftr1dinternaleven(ref buf2, p, ref buf3, plan, _params);
+                    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, _params);
+                    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.ftcomplexfftplan((q+n-1)/2, 1, plan, _params);
+                
+                //
+                // 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, _params);
+                
+                //
+                // 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, _params);
+                    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, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+            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_];
+                }
+            }
+        }
+
+
+    }
+}
+
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/integration.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/integration.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/integration.cs	(revision 17158)
@@ -0,0 +1,3775 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 164
+#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, null);
+    }
+    
+    public static void gqgeneraterec(double[] alpha, double[] beta, double mu0, int n, out int info, out double[] x, out double[] w, alglib.xparams _params)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgeneraterec(alpha, beta, mu0, n, ref info, ref x, ref w, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgenerategausslobattorec(alpha, beta, mu0, a, b, n, ref info, ref x, ref w, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void gqgenerategaussradaurec(double[] alpha, double[] beta, double mu0, double a, int n, out int info, out double[] x, out double[] w, alglib.xparams _params)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgenerategaussradaurec(alpha, beta, mu0, a, n, ref info, ref x, ref w, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void gqgenerategausslegendre(int n, out int info, out double[] x, out double[] w, alglib.xparams _params)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgenerategausslegendre(n, ref info, ref x, ref w, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void gqgenerategaussjacobi(int n, double alpha, double beta, out int info, out double[] x, out double[] w, alglib.xparams _params)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgenerategaussjacobi(n, alpha, beta, ref info, ref x, ref w, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void gqgenerategausslaguerre(int n, double alpha, out int info, out double[] x, out double[] w, alglib.xparams _params)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgenerategausslaguerre(n, alpha, ref info, ref x, ref w, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void gqgenerategausshermite(int n, out int info, out double[] x, out double[] w, alglib.xparams _params)
+    {
+        info = 0;
+        x = new double[0];
+        w = new double[0];
+        gq.gqgenerategausshermite(n, ref info, ref x, ref w, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void gkqgeneraterec(double[] alpha, double[] beta, double mu0, int n, out int info, out double[] x, out double[] wkronrod, out double[] wgauss, alglib.xparams _params)
+    {
+        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, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void gkqgenerategausslegendre(int n, out int info, out double[] x, out double[] wkronrod, out double[] wgauss, alglib.xparams _params)
+    {
+        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, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void gkqgenerategaussjacobi(int n, double alpha, double beta, out int info, out double[] x, out double[] wkronrod, out double[] wgauss, alglib.xparams _params)
+    {
+        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, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void gkqlegendrecalc(int n, out int info, out double[] x, out double[] wkronrod, out double[] wgauss, alglib.xparams _params)
+    {
+        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, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void gkqlegendretbl(int n, out double[] x, out double[] wkronrod, out double[] wgauss, out double eps, alglib.xparams _params)
+    {
+        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, _params);
+    }
+
+}
+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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new autogkreport((autogk.autogkreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new autogkstate((autogk.autogkstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void autogksmooth(double a, double b, out autogkstate state, alglib.xparams _params)
+    {
+        state = new autogkstate();
+        autogk.autogksmooth(a, b, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void autogksmoothw(double a, double b, double xwidth, out autogkstate state, alglib.xparams _params)
+    {
+        state = new autogkstate();
+        autogk.autogksmoothw(a, b, xwidth, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void autogksingular(double a, double b, double alpha, double beta, out autogkstate state, alglib.xparams _params)
+    {
+        state = new autogkstate();
+        autogk.autogksingular(a, b, alpha, beta, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return autogk.autogkiteration(state.innerobj, null);
+    }
+    
+    public static bool autogkiteration(autogkstate state, alglib.xparams _params)
+    {
+    
+        return autogk.autogkiteration(state.innerobj, _params);
+    }
+
+
+    /*************************************************************************
+    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)
+    {
+        autogkintegrate(state, func, obj, null);
+    }
+        
+    public static void autogkintegrate(autogkstate state, integrator1_func func, object obj, alglib.xparams _params)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'autogkintegrate()' (func is null)");
+        while( alglib.autogkiteration(state, _params) )
+        {
+            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, null);
+    }
+    
+    public static void autogkresults(autogkstate state, out double v, out autogkreport rep, alglib.xparams _params)
+    {
+        v = 0;
+        rep = new autogkreport();
+        autogk.autogkresults(state.innerobj, ref v, rep.innerobj, _params);
+    }
+
+}
+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,
+            alglib.xparams _params)
+        {
+            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, _params) )
+            {
+                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,
+            alglib.xparams _params)
+        {
+            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, _params) )
+            {
+                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,
+            alglib.xparams _params)
+        {
+            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, _params) )
+            {
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params)+gammafunc.lngamma(beta+1, ref s, _params)-gammafunc.lngamma(apb+2, ref s, _params);
+            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, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            }
+            else
+            {
+                gkqlegendrecalc(n, ref info, ref x, ref wkronrod, ref wgauss, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params)+gammafunc.lngamma(beta+1, ref s, _params)-gammafunc.lngamma(apb+2, ref s, _params);
+            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, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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 : apobject
+        {
+            public int terminationtype;
+            public int nfev;
+            public int nintervals;
+            public autogkreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                autogkreport _result = new autogkreport();
+                _result.terminationtype = terminationtype;
+                _result.nfev = nfev;
+                _result.nintervals = nintervals;
+                return _result;
+            }
+        };
+
+
+        public class autogkinternalstate : apobject
+        {
+            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()
+            {
+                init();
+            }
+            public override void init()
+            {
+                heap = new double[0,0];
+                qn = new double[0];
+                wg = new double[0];
+                wk = new double[0];
+                wr = new double[0];
+                rstate = new rcommstate();
+            }
+            public override alglib.apobject make_copy()
+            {
+                autogkinternalstate _result = new autogkinternalstate();
+                _result.a = a;
+                _result.b = b;
+                _result.eps = eps;
+                _result.xwidth = xwidth;
+                _result.x = x;
+                _result.f = f;
+                _result.info = info;
+                _result.r = r;
+                _result.heap = (double[,])heap.Clone();
+                _result.heapsize = heapsize;
+                _result.heapwidth = heapwidth;
+                _result.heapused = heapused;
+                _result.sumerr = sumerr;
+                _result.sumabs = sumabs;
+                _result.qn = (double[])qn.Clone();
+                _result.wg = (double[])wg.Clone();
+                _result.wk = (double[])wk.Clone();
+                _result.wr = (double[])wr.Clone();
+                _result.n = n;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        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 : apobject
+        {
+            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()
+            {
+                init();
+            }
+            public override void init()
+            {
+                internalstate = new autogkinternalstate();
+                rstate = new rcommstate();
+            }
+            public override alglib.apobject make_copy()
+            {
+                autogkstate _result = new autogkstate();
+                _result.a = a;
+                _result.b = b;
+                _result.alpha = alpha;
+                _result.beta = beta;
+                _result.xwidth = xwidth;
+                _result.x = x;
+                _result.xminusa = xminusa;
+                _result.bminusx = bminusx;
+                _result.needf = needf;
+                _result.f = f;
+                _result.wrappermode = wrappermode;
+                _result.internalstate = (autogkinternalstate)internalstate.make_copy();
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.v = v;
+                _result.terminationtype = terminationtype;
+                _result.nfev = nfev;
+                _result.nintervals = nintervals;
+                return _result;
+            }
+        };
+
+
+
+
+        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.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 = 359;
+                tmp = -58;
+                eps = -919;
+                a = -909;
+                b = 81;
+                x = 255;
+                t = 74;
+                alpha = -788;
+                beta = 809;
+                v1 = 205;
+                v2 = -838;
+            }
+            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, _params);
+        lbl_5:
+            if( !autogkinternaliteration(state.internalstate, _params) )
+            {
+                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, _params);
+        lbl_9:
+            if( !autogkinternaliteration(state.internalstate, _params) )
+            {
+                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, _params);
+        lbl_11:
+            if( !autogkinternaliteration(state.internalstate, _params) )
+            {
+                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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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 = 939;
+                j = -526;
+                ns = 763;
+                info = -541;
+                c1 = -698;
+                c2 = -900;
+                intg = -318;
+                intk = -940;
+                inta = 1016;
+                v = -229;
+                ta = -536;
+                tb = 487;
+                qeps = -115;
+            }
+            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, _params);
+            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, _params);
+            }
+            
+            //
+            // 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, _params);
+            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, _params);
+            mheappush(ref state.heap, state.heapused, state.heapwidth, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/interpolation.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/interpolation.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/interpolation.cs	(revision 17158)
@@ -0,0 +1,47890 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 164
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Buffer  object  which  is  used  to  perform  evaluation  requests  in  the
+    multithreaded mode (multiple threads working with same IDW object).
+
+    This object should be created with idwcreatecalcbuffer().
+    *************************************************************************/
+    public class idwcalcbuffer : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public idwcalcbuffer()
+        {
+            _innerobj = new idw.idwcalcbuffer();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new idwcalcbuffer((idw.idwcalcbuffer)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private idw.idwcalcbuffer _innerobj;
+        public idw.idwcalcbuffer innerobj { get { return _innerobj; } }
+        public idwcalcbuffer(idw.idwcalcbuffer obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    IDW (Inverse Distance Weighting) model object.
+    *************************************************************************/
+    public class idwmodel : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public idwmodel()
+        {
+            _innerobj = new idw.idwmodel();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new idwmodel((idw.idwmodel)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private idw.idwmodel _innerobj;
+        public idw.idwmodel innerobj { get { return _innerobj; } }
+        public idwmodel(idw.idwmodel obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    Builder object used to generate IDW (Inverse Distance Weighting) model.
+    *************************************************************************/
+    public class idwbuilder : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public idwbuilder()
+        {
+            _innerobj = new idw.idwbuilder();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new idwbuilder((idw.idwbuilder)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private idw.idwbuilder _innerobj;
+        public idw.idwbuilder innerobj { get { return _innerobj; } }
+        public idwbuilder(idw.idwbuilder obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    IDW fitting report:
+        rmserror        RMS error
+        avgerror        average error
+        maxerror        maximum error
+        r2              coefficient of determination,  R-squared, 1-RSS/TSS
+    *************************************************************************/
+    public class idwreport : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public double rmserror { get { return _innerobj.rmserror; } set { _innerobj.rmserror = value; } }
+        public double avgerror { get { return _innerobj.avgerror; } set { _innerobj.avgerror = value; } }
+        public double maxerror { get { return _innerobj.maxerror; } set { _innerobj.maxerror = value; } }
+        public double r2 { get { return _innerobj.r2; } set { _innerobj.r2 = value; } }
+    
+        public idwreport()
+        {
+            _innerobj = new idw.idwreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new idwreport((idw.idwreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private idw.idwreport _innerobj;
+        public idw.idwreport innerobj { get { return _innerobj; } }
+        public idwreport(idw.idwreport 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 idwserialize(idwmodel obj, out string s_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        idw.idwalloc(s, obj.innerobj, null);
+        s.sstart_str();
+        idw.idwserialize(s, obj.innerobj, null);
+        s.stop();
+        s_out = s.get_string();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from string.
+    *************************************************************************/
+    public static void idwunserialize(string s_in, out idwmodel obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new idwmodel();
+        s.ustart_str(s_in);
+        idw.idwunserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to stream.
+    
+    Data stream generated by this function is same as  string  representation
+    generated  by  string  version  of  serializer - alphanumeric characters,
+    dots, underscores, minus signs, which are grouped into words separated by
+    spaces and CR+LF.
+    
+    We recommend you to read comments on string version of serializer to find
+    out more about serialization of AlGLIB objects.
+    *************************************************************************/
+    public static void idwserialize(idwmodel obj, System.IO.Stream stream_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        idw.idwalloc(s, obj.innerobj, null);
+        s.sstart_stream(stream_out);
+        idw.idwserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from stream.
+    *************************************************************************/
+    public static void idwunserialize(System.IO.Stream stream_in, out idwmodel obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new idwmodel();
+        s.ustart_stream(stream_in);
+        idw.idwunserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+    
+    /*************************************************************************
+    This function creates buffer  structure  which  can  be  used  to  perform
+    parallel  IDW  model  evaluations  (with  one  IDW  model  instance  being
+    used from multiple threads, as long as  different  threads  use  different
+    instances of buffer).
+
+    This buffer object can be used with  idwtscalcbuf()  function  (here  "ts"
+    stands for "thread-safe", "buf" is a suffix which denotes  function  which
+    reuses previously allocated output space).
+
+    How to use it:
+    * create IDW model structure or load it from file
+    * call idwcreatecalcbuffer(), once per thread working with IDW model  (you
+      should call this function only AFTER model initialization, see below for
+      more information)
+    * call idwtscalcbuf() from different threads,  with  each  thread  working
+      with its own copy of buffer object.
+
+    INPUT PARAMETERS
+        S           -   IDW model
+
+    OUTPUT PARAMETERS
+        Buf         -   external buffer.
+
+
+    IMPORTANT: buffer object should be used only with  IDW model object  which
+               was used to initialize buffer. Any attempt to use buffer   with
+               different object is dangerous - you may  get  memory  violation
+               error because sizes of internal arrays do not fit to dimensions
+               of the IDW structure.
+
+    IMPORTANT: you  should  call  this function only for model which was built
+               with model builder (or unserialized from file). Sizes  of  some
+               internal structures are determined only after model  is  built,
+               so buffer object created before model construction  stage  will
+               be useless (and any attempt to use it will result in exception).
+
+      -- ALGLIB --
+         Copyright 22.10.2018 by Sergey Bochkanov
+    *************************************************************************/
+    public static void idwcreatecalcbuffer(idwmodel s, out idwcalcbuffer buf)
+    {
+        buf = new idwcalcbuffer();
+        idw.idwcreatecalcbuffer(s.innerobj, buf.innerobj, null);
+    }
+    
+    public static void idwcreatecalcbuffer(idwmodel s, out idwcalcbuffer buf, alglib.xparams _params)
+    {
+        buf = new idwcalcbuffer();
+        idw.idwcreatecalcbuffer(s.innerobj, buf.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine creates builder object used  to  generate IDW  model  from
+    irregularly sampled (scattered) dataset.  Multidimensional  scalar/vector-
+    -valued are supported.
+
+    Builder object is used to fit model to data as follows:
+    * builder object is created with idwbuildercreate() function
+    * dataset is added with idwbuildersetpoints() function
+    * one of the modern IDW algorithms is chosen with either:
+      * idwbuildersetalgomstab()            - Multilayer STABilized algorithm (interpolation)
+      Alternatively, one of the textbook algorithms can be chosen (not recommended):
+      * idwbuildersetalgotextbookshepard()  - textbook Shepard algorithm
+      * idwbuildersetalgotextbookmodshepard()-textbook modified Shepard algorithm
+    * finally, model construction is performed with idwfit() function.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        NX  -   dimensionality of the argument, NX>=1
+        NY  -   dimensionality of the function being modeled, NY>=1;
+                NY=1 corresponds to classic scalar function, NY>=1 corresponds
+                to vector-valued function.
+
+    OUTPUT PARAMETERS:
+        State-  builder object
+
+      -- ALGLIB PROJECT --
+         Copyright 22.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwbuildercreate(int nx, int ny, out idwbuilder state)
+    {
+        state = new idwbuilder();
+        idw.idwbuildercreate(nx, ny, state.innerobj, null);
+    }
+    
+    public static void idwbuildercreate(int nx, int ny, out idwbuilder state, alglib.xparams _params)
+    {
+        state = new idwbuilder();
+        idw.idwbuildercreate(nx, ny, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function changes number of layers used by IDW-MSTAB algorithm.
+
+    The more layers you have, the finer details can  be  reproduced  with  IDW
+    model. The less layers you have, the less memory and CPU time is  consumed
+    by the model.
+
+    Memory consumption grows linearly with layers count,  running  time  grows
+    sub-linearly.
+
+    The default number of layers is 16, which allows you to reproduce  details
+    at distance down to SRad/65536. You will rarely need to change it.
+
+    INPUT PARAMETERS:
+        State   -   builder object
+        NLayers -   NLayers>=1, the number of layers used by the model.
+
+      -- ALGLIB --
+         Copyright 22.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwbuildersetnlayers(idwbuilder state, int nlayers)
+    {
+    
+        idw.idwbuildersetnlayers(state.innerobj, nlayers, null);
+    }
+    
+    public static void idwbuildersetnlayers(idwbuilder state, int nlayers, alglib.xparams _params)
+    {
+    
+        idw.idwbuildersetnlayers(state.innerobj, nlayers, _params);
+    }
+    
+    /*************************************************************************
+    This function adds dataset to the builder object.
+
+    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:
+        State   -   builder object
+        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
+                    specified, in  this  case  only leading [N,NX+NY] elements
+                    will be used.
+        N       -   number of points in the dataset, N>=0.
+
+      -- ALGLIB --
+         Copyright 22.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwbuildersetpoints(idwbuilder state, double[,] xy, int n)
+    {
+    
+        idw.idwbuildersetpoints(state.innerobj, xy, n, null);
+    }
+    
+    public static void idwbuildersetpoints(idwbuilder state, double[,] xy, int n, alglib.xparams _params)
+    {
+    
+        idw.idwbuildersetpoints(state.innerobj, xy, n, _params);
+    }
+            
+    public static void idwbuildersetpoints(idwbuilder state, double[,] xy)
+    {
+        int n;
+    
+    
+        n = ap.rows(xy);
+        idw.idwbuildersetpoints(state.innerobj, xy, n, null);
+    
+        return;
+    }
+            
+    public static void idwbuildersetpoints(idwbuilder state, double[,] xy, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.rows(xy);
+        idw.idwbuildersetpoints(state.innerobj, xy, n, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function sets IDW model  construction  algorithm  to  the  Multilayer
+    Stabilized IDW method (IDW-MSTAB), a  latest  incarnation  of  the inverse
+    distance weighting interpolation which fixes shortcomings of  the original
+    and modified Shepard's variants.
+
+    The distinctive features of IDW-MSTAB are:
+    1) exact interpolation  is  pursued  (as  opposed  to  fitting  and  noise
+       suppression)
+    2) improved robustness when compared with that of other algorithms:
+       * MSTAB shows almost no strange  fitting  artifacts  like  ripples  and
+         sharp spikes (unlike N-dimensional splines and HRBFs)
+       * MSTAB does not return function values far from the  interval  spanned
+         by the dataset; say, if all your points have |f|<=1, you  can be sure
+         that model value won't deviate too much from [-1,+1]
+    3) good model construction time competing with that of HRBFs  and  bicubic
+       splines
+    4) ability to work with any number of dimensions, starting from NX=1
+
+    The drawbacks of IDW-MSTAB (and all IDW algorithms in general) are:
+    1) dependence of the model evaluation time on the search radius
+    2) bad extrapolation properties, models built by this method  are  usually
+       conservative in their predictions
+
+    Thus, IDW-MSTAB is  a  good  "default"  option  if  you  want  to  perform
+    scattered multidimensional interpolation. Although it has  its  drawbacks,
+    it is easy to use and robust, which makes it a good first step.
+
+
+    INPUT PARAMETERS:
+        State   -   builder object
+        SRad    -   initial search radius, SRad>0 is required. A model  value
+                    is obtained by "smart" averaging  of  the  dataset  points
+                    within search radius.
+
+    NOTE 1: IDW interpolation can  correctly  handle  ANY  dataset,  including
+            datasets with non-distinct points. In case non-distinct points are
+            found, an average value for this point will be calculated.
+
+    NOTE 2: the memory requirements for model storage are O(NPoints*NLayers).
+            The model construction needs twice as much memory as model storage.
+
+    NOTE 3: by default 16 IDW layers are built which is enough for most cases.
+            You can change this parameter with idwbuildersetnlayers()  method.
+            Larger values may be necessary if you need to reproduce  extrafine
+            details at distances smaller than SRad/65536.  Smaller value   may
+            be necessary if you have to save memory and  computing  time,  and
+            ready to sacrifice some model quality.
+
+
+    ALGORITHM DESCRIPTION
+
+    ALGLIB implementation of IDW is somewhat similar to the modified Shepard's
+    method (one with search radius R) but overcomes several of its  drawbacks,
+    namely:
+    1) a tendency to show stepwise behavior for uniform datasets
+    2) a tendency to show terrible interpolation properties for highly
+       nonuniform datasets which often arise in geospatial tasks
+      (function values are densely sampled across multiple separated
+      "tracks")
+
+    IDW-MSTAB method performs several passes over dataset and builds a sequence
+    of progressively refined IDW models  (layers),  which starts from one with
+    largest search radius SRad  and continues to smaller  search  radii  until
+    required number of  layers  is  built.  Highest  layers  reproduce  global
+    behavior of the target function at larger distances  whilst  lower  layers
+    reproduce fine details at smaller distances.
+
+    Each layer is an IDW model built with following modifications:
+    * weights go to zero when distance approach to the current search radius
+    * an additional regularizing term is added to the distance: w=1/(d^2+lambda)
+    * an additional fictional term with unit weight and zero function value is
+      added in order to promote continuity  properties  at  the  isolated  and
+      boundary points
+
+    By default, 16 layers is built, which is enough for most  cases.  You  can
+    change this parameter with idwbuildersetnlayers() method.
+
+      -- ALGLIB --
+         Copyright 22.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwbuildersetalgomstab(idwbuilder state, double srad)
+    {
+    
+        idw.idwbuildersetalgomstab(state.innerobj, srad, null);
+    }
+    
+    public static void idwbuildersetalgomstab(idwbuilder state, double srad, alglib.xparams _params)
+    {
+    
+        idw.idwbuildersetalgomstab(state.innerobj, srad, _params);
+    }
+    
+    /*************************************************************************
+    This function sets  IDW  model  construction  algorithm  to  the  textbook
+    Shepard's algorithm with custom (user-specified) power parameter.
+
+    IMPORTANT: we do NOT recommend using textbook IDW algorithms because  they
+               have terrible interpolation properties. Use MSTAB in all cases.
+
+    INPUT PARAMETERS:
+        State   -   builder object
+        P       -   power parameter, P>0; good value to start with is 2.0
+
+    NOTE 1: IDW interpolation can  correctly  handle  ANY  dataset,  including
+            datasets with non-distinct points. In case non-distinct points are
+            found, an average value for this point will be calculated.
+
+      -- ALGLIB --
+         Copyright 22.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwbuildersetalgotextbookshepard(idwbuilder state, double p)
+    {
+    
+        idw.idwbuildersetalgotextbookshepard(state.innerobj, p, null);
+    }
+    
+    public static void idwbuildersetalgotextbookshepard(idwbuilder state, double p, alglib.xparams _params)
+    {
+    
+        idw.idwbuildersetalgotextbookshepard(state.innerobj, p, _params);
+    }
+    
+    /*************************************************************************
+    This function sets  IDW  model  construction  algorithm  to the 'textbook'
+    modified Shepard's algorithm with user-specified search radius.
+
+    IMPORTANT: we do NOT recommend using textbook IDW algorithms because  they
+               have terrible interpolation properties. Use MSTAB in all cases.
+
+    INPUT PARAMETERS:
+        State   -   builder object
+        R       -   search radius
+
+    NOTE 1: IDW interpolation can  correctly  handle  ANY  dataset,  including
+            datasets with non-distinct points. In case non-distinct points are
+            found, an average value for this point will be calculated.
+
+      -- ALGLIB --
+         Copyright 22.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwbuildersetalgotextbookmodshepard(idwbuilder state, double r)
+    {
+    
+        idw.idwbuildersetalgotextbookmodshepard(state.innerobj, r, null);
+    }
+    
+    public static void idwbuildersetalgotextbookmodshepard(idwbuilder state, double r, alglib.xparams _params)
+    {
+    
+        idw.idwbuildersetalgotextbookmodshepard(state.innerobj, r, _params);
+    }
+    
+    /*************************************************************************
+    This function sets prior term (model value at infinity) as  user-specified
+    value.
+
+    INPUT PARAMETERS:
+        S       -   spline builder
+        V       -   value for user-defined prior
+
+    NOTE: for vector-valued models all components of the prior are set to same
+          user-specified value
+
+      -- ALGLIB --
+         Copyright 29.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwbuildersetuserterm(idwbuilder state, double v)
+    {
+    
+        idw.idwbuildersetuserterm(state.innerobj, v, null);
+    }
+    
+    public static void idwbuildersetuserterm(idwbuilder state, double v, alglib.xparams _params)
+    {
+    
+        idw.idwbuildersetuserterm(state.innerobj, v, _params);
+    }
+    
+    /*************************************************************************
+    This function sets constant prior term (model value at infinity).
+
+    Constant prior term is determined as mean value over dataset.
+
+    INPUT PARAMETERS:
+        S       -   spline builder
+
+      -- ALGLIB --
+         Copyright 29.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwbuildersetconstterm(idwbuilder state)
+    {
+    
+        idw.idwbuildersetconstterm(state.innerobj, null);
+    }
+    
+    public static void idwbuildersetconstterm(idwbuilder state, alglib.xparams _params)
+    {
+    
+        idw.idwbuildersetconstterm(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function sets zero prior term (model value at infinity).
+
+    INPUT PARAMETERS:
+        S       -   spline builder
+
+      -- ALGLIB --
+         Copyright 29.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwbuildersetzeroterm(idwbuilder state)
+    {
+    
+        idw.idwbuildersetzeroterm(state.innerobj, null);
+    }
+    
+    public static void idwbuildersetzeroterm(idwbuilder state, alglib.xparams _params)
+    {
+    
+        idw.idwbuildersetzeroterm(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    IDW interpolation: scalar target, 1-dimensional argument
+
+    NOTE: this function modifies internal temporaries of the  IDW  model, thus
+          IT IS NOT  THREAD-SAFE!  If  you  want  to  perform  parallel  model
+          evaluation from the multiple threads, use idwtscalcbuf()  with  per-
+          thread buffer object.
+
+    INPUT PARAMETERS:
+        S   -   IDW interpolant built with IDW builder
+        X0  -   argument value
+
+    Result:
+        IDW interpolant S(X0)
+
+      -- ALGLIB --
+         Copyright 22.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static double idwcalc1(idwmodel s, double x0)
+    {
+    
+        return idw.idwcalc1(s.innerobj, x0, null);
+    }
+    
+    public static double idwcalc1(idwmodel s, double x0, alglib.xparams _params)
+    {
+    
+        return idw.idwcalc1(s.innerobj, x0, _params);
+    }
+    
+    /*************************************************************************
+    IDW interpolation: scalar target, 2-dimensional argument
+
+    NOTE: this function modifies internal temporaries of the  IDW  model, thus
+          IT IS NOT  THREAD-SAFE!  If  you  want  to  perform  parallel  model
+          evaluation from the multiple threads, use idwtscalcbuf()  with  per-
+          thread buffer object.
+
+    INPUT PARAMETERS:
+        S       -   IDW interpolant built with IDW builder
+        X0, X1  -   argument value
+
+    Result:
+        IDW interpolant S(X0,X1)
+
+      -- ALGLIB --
+         Copyright 22.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static double idwcalc2(idwmodel s, double x0, double x1)
+    {
+    
+        return idw.idwcalc2(s.innerobj, x0, x1, null);
+    }
+    
+    public static double idwcalc2(idwmodel s, double x0, double x1, alglib.xparams _params)
+    {
+    
+        return idw.idwcalc2(s.innerobj, x0, x1, _params);
+    }
+    
+    /*************************************************************************
+    IDW interpolation: scalar target, 3-dimensional argument
+
+    NOTE: this function modifies internal temporaries of the  IDW  model, thus
+          IT IS NOT  THREAD-SAFE!  If  you  want  to  perform  parallel  model
+          evaluation from the multiple threads, use idwtscalcbuf()  with  per-
+          thread buffer object.
+
+    INPUT PARAMETERS:
+        S       -   IDW interpolant built with IDW builder
+        X0,X1,X2-   argument value
+
+    Result:
+        IDW interpolant S(X0,X1,X2)
+
+      -- ALGLIB --
+         Copyright 22.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static double idwcalc3(idwmodel s, double x0, double x1, double x2)
+    {
+    
+        return idw.idwcalc3(s.innerobj, x0, x1, x2, null);
+    }
+    
+    public static double idwcalc3(idwmodel s, double x0, double x1, double x2, alglib.xparams _params)
+    {
+    
+        return idw.idwcalc3(s.innerobj, x0, x1, x2, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates values of the IDW 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  idwcalc1(),
+    idwcalc2() or idwcalc3().
+
+    NOTE: this function modifies internal temporaries of the  IDW  model, thus
+          IT IS NOT  THREAD-SAFE!  If  you  want  to  perform  parallel  model
+          evaluation from the multiple threads, use idwtscalcbuf()  with  per-
+          thread buffer object.
+
+    INPUT PARAMETERS:
+        S       -   IDW 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 will  be
+                    reallocated after call to this function. In case you  want
+                    to reuse previously allocated Y, you may use idwcalcbuf(),
+                    which reallocates Y only when it is too small.
+
+      -- ALGLIB --
+         Copyright 22.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwcalc(idwmodel s, double[] x, out double[] y)
+    {
+        y = new double[0];
+        idw.idwcalc(s.innerobj, x, ref y, null);
+    }
+    
+    public static void idwcalc(idwmodel s, double[] x, out double[] y, alglib.xparams _params)
+    {
+        y = new double[0];
+        idw.idwcalc(s.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates values of the IDW model at the given point.
+
+    Same as idwcalc(), but does not reallocate Y when in is large enough to
+    store function values.
+
+    NOTE: this function modifies internal temporaries of the  IDW  model, thus
+          IT IS NOT  THREAD-SAFE!  If  you  want  to  perform  parallel  model
+          evaluation from the multiple threads, use idwtscalcbuf()  with  per-
+          thread buffer object.
+
+    INPUT PARAMETERS:
+        S       -   IDW 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 22.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwcalcbuf(idwmodel s, double[] x, ref double[] y)
+    {
+    
+        idw.idwcalcbuf(s.innerobj, x, ref y, null);
+    }
+    
+    public static void idwcalcbuf(idwmodel s, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        idw.idwcalcbuf(s.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates values of the IDW model at the given point, using
+    external  buffer  object  (internal  temporaries  of  IDW  model  are  not
+    modified).
+
+    This function allows to use same IDW model object  in  different  threads,
+    assuming  that  different   threads  use different instances of the buffer
+    structure.
+
+    INPUT PARAMETERS:
+        S       -   IDW model, may be shared between different threads
+        Buf     -   buffer object created for this particular instance of  IDW
+                    model with idwcreatecalcbuffer().
+        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 idwtscalcbuf(idwmodel s, idwcalcbuffer buf, double[] x, ref double[] y)
+    {
+    
+        idw.idwtscalcbuf(s.innerobj, buf.innerobj, x, ref y, null);
+    }
+    
+    public static void idwtscalcbuf(idwmodel s, idwcalcbuffer buf, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        idw.idwtscalcbuf(s.innerobj, buf.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function fits IDW model to the dataset using current IDW construction
+    algorithm. A model being built and fitting report are returned.
+
+    INPUT PARAMETERS:
+        State   -   builder object
+
+    OUTPUT PARAMETERS:
+        Model   -   an IDW model built with current algorithm
+        Rep     -   model fitting report, fields of this structure contain
+                    information about average fitting errors.
+
+    NOTE: although IDW-MSTAB algorithm is an  interpolation  method,  i.e.  it
+          tries to fit the model exactly, it can  handle  datasets  with  non-
+          distinct points which can not be fit exactly; in such  cases  least-
+          squares fitting is performed.
+
+      -- ALGLIB --
+         Copyright 22.10.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void idwfit(idwbuilder state, out idwmodel model, out idwreport rep)
+    {
+        model = new idwmodel();
+        rep = new idwreport();
+        idw.idwfit(state.innerobj, model.innerobj, rep.innerobj, null);
+    }
+    
+    public static void idwfit(idwbuilder state, out idwmodel model, out idwreport rep, alglib.xparams _params)
+    {
+        model = new idwmodel();
+        rep = new idwreport();
+        idw.idwfit(state.innerobj, model.innerobj, rep.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Barycentric interpolant.
+    *************************************************************************/
+    public class barycentricinterpolant : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public barycentricinterpolant()
+        {
+            _innerobj = new ratint.barycentricinterpolant();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new barycentricinterpolant((ratint.barycentricinterpolant)_innerobj.make_copy());
+        }
+    
+        //
+        // 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)
+    {
+    
+        return ratint.barycentriccalc(b.innerobj, t, null);
+    }
+    
+    public static double barycentriccalc(barycentricinterpolant b, double t, alglib.xparams _params)
+    {
+    
+        return ratint.barycentriccalc(b.innerobj, t, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void barycentricdiff1(barycentricinterpolant b, double t, out double f, out double df, alglib.xparams _params)
+    {
+        f = 0;
+        df = 0;
+        ratint.barycentricdiff1(b.innerobj, t, ref f, ref df, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void barycentricdiff2(barycentricinterpolant b, double t, out double f, out double df, out double d2f, alglib.xparams _params)
+    {
+        f = 0;
+        df = 0;
+        d2f = 0;
+        ratint.barycentricdiff2(b.innerobj, t, ref f, ref df, ref d2f, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void barycentriclintransx(barycentricinterpolant b, double ca, double cb, alglib.xparams _params)
+    {
+    
+        ratint.barycentriclintransx(b.innerobj, ca, cb, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void barycentriclintransy(barycentricinterpolant b, double ca, double cb, alglib.xparams _params)
+    {
+    
+        ratint.barycentriclintransy(b.innerobj, ca, cb, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void barycentricunpack(barycentricinterpolant b, out int n, out double[] x, out double[] y, out double[] w, alglib.xparams _params)
+    {
+        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, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void barycentricbuildxyw(double[] x, double[] y, double[] w, int n, out barycentricinterpolant b, alglib.xparams _params)
+    {
+        b = new barycentricinterpolant();
+        ratint.barycentricbuildxyw(x, y, w, n, b.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void barycentricbuildfloaterhormann(double[] x, double[] y, int n, int d, out barycentricinterpolant b, alglib.xparams _params)
+    {
+        b = new barycentricinterpolant();
+        ratint.barycentricbuildfloaterhormann(x, y, n, d, b.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+    
+    /*************************************************************************
+    Fits least squares (LS) circle (or NX-dimensional sphere) to data  (a  set
+    of points in NX-dimensional space).
+
+    Least squares circle minimizes sum of squared deviations between distances
+    from points to the center and  some  "candidate"  radius,  which  is  also
+    fitted to the data.
+
+    INPUT PARAMETERS:
+        XY      -   array[NPoints,NX] (or larger), contains dataset.
+                    One row = one point in NX-dimensional space.
+        NPoints -   dataset size, NPoints>0
+        NX      -   space dimensionality, NX>0 (1, 2, 3, 4, 5 and so on)
+
+    OUTPUT PARAMETERS:
+        CX      -   central point for a sphere
+        R       -   radius
+
+      -- ALGLIB --
+         Copyright 07.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fitspherels(double[,] xy, int npoints, int nx, out double[] cx, out double r)
+    {
+        cx = new double[0];
+        r = 0;
+        fitsphere.fitspherels(xy, npoints, nx, ref cx, ref r, null);
+    }
+    
+    public static void fitspherels(double[,] xy, int npoints, int nx, out double[] cx, out double r, alglib.xparams _params)
+    {
+        cx = new double[0];
+        r = 0;
+        fitsphere.fitspherels(xy, npoints, nx, ref cx, ref r, _params);
+    }
+    
+    /*************************************************************************
+    Fits minimum circumscribed (MC) circle (or NX-dimensional sphere) to  data
+    (a set of points in NX-dimensional space).
+
+    INPUT PARAMETERS:
+        XY      -   array[NPoints,NX] (or larger), contains dataset.
+                    One row = one point in NX-dimensional space.
+        NPoints -   dataset size, NPoints>0
+        NX      -   space dimensionality, NX>0 (1, 2, 3, 4, 5 and so on)
+
+    OUTPUT PARAMETERS:
+        CX      -   central point for a sphere
+        RHi     -   radius
+
+    NOTE: this function is an easy-to-use wrapper around more powerful "expert"
+          function fitspherex().
+
+          This  wrapper  is optimized  for  ease of use and stability - at the
+          cost of somewhat lower  performance  (we  have  to  use  very  tight
+          stopping criteria for inner optimizer because we want to  make  sure
+          that it will converge on any dataset).
+
+          If you are ready to experiment with settings of  "expert"  function,
+          you can achieve ~2-4x speedup over standard "bulletproof" settings.
+
+
+      -- ALGLIB --
+         Copyright 14.04.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fitspheremc(double[,] xy, int npoints, int nx, out double[] cx, out double rhi)
+    {
+        cx = new double[0];
+        rhi = 0;
+        fitsphere.fitspheremc(xy, npoints, nx, ref cx, ref rhi, null);
+    }
+    
+    public static void fitspheremc(double[,] xy, int npoints, int nx, out double[] cx, out double rhi, alglib.xparams _params)
+    {
+        cx = new double[0];
+        rhi = 0;
+        fitsphere.fitspheremc(xy, npoints, nx, ref cx, ref rhi, _params);
+    }
+    
+    /*************************************************************************
+    Fits maximum inscribed circle (or NX-dimensional sphere) to data (a set of
+    points in NX-dimensional space).
+
+    INPUT PARAMETERS:
+        XY      -   array[NPoints,NX] (or larger), contains dataset.
+                    One row = one point in NX-dimensional space.
+        NPoints -   dataset size, NPoints>0
+        NX      -   space dimensionality, NX>0 (1, 2, 3, 4, 5 and so on)
+
+    OUTPUT PARAMETERS:
+        CX      -   central point for a sphere
+        RLo     -   radius
+
+    NOTE: this function is an easy-to-use wrapper around more powerful "expert"
+          function fitspherex().
+
+          This  wrapper  is optimized  for  ease of use and stability - at the
+          cost of somewhat lower  performance  (we  have  to  use  very  tight
+          stopping criteria for inner optimizer because we want to  make  sure
+          that it will converge on any dataset).
+
+          If you are ready to experiment with settings of  "expert"  function,
+          you can achieve ~2-4x speedup over standard "bulletproof" settings.
+
+
+      -- ALGLIB --
+         Copyright 14.04.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fitspheremi(double[,] xy, int npoints, int nx, out double[] cx, out double rlo)
+    {
+        cx = new double[0];
+        rlo = 0;
+        fitsphere.fitspheremi(xy, npoints, nx, ref cx, ref rlo, null);
+    }
+    
+    public static void fitspheremi(double[,] xy, int npoints, int nx, out double[] cx, out double rlo, alglib.xparams _params)
+    {
+        cx = new double[0];
+        rlo = 0;
+        fitsphere.fitspheremi(xy, npoints, nx, ref cx, ref rlo, _params);
+    }
+    
+    /*************************************************************************
+    Fits minimum zone circle (or NX-dimensional sphere)  to  data  (a  set  of
+    points in NX-dimensional space).
+
+    INPUT PARAMETERS:
+        XY      -   array[NPoints,NX] (or larger), contains dataset.
+                    One row = one point in NX-dimensional space.
+        NPoints -   dataset size, NPoints>0
+        NX      -   space dimensionality, NX>0 (1, 2, 3, 4, 5 and so on)
+
+    OUTPUT PARAMETERS:
+        CX      -   central point for a sphere
+        RLo     -   radius of inscribed circle
+        RHo     -   radius of circumscribed circle
+
+    NOTE: this function is an easy-to-use wrapper around more powerful "expert"
+          function fitspherex().
+
+          This  wrapper  is optimized  for  ease of use and stability - at the
+          cost of somewhat lower  performance  (we  have  to  use  very  tight
+          stopping criteria for inner optimizer because we want to  make  sure
+          that it will converge on any dataset).
+
+          If you are ready to experiment with settings of  "expert"  function,
+          you can achieve ~2-4x speedup over standard "bulletproof" settings.
+
+
+      -- ALGLIB --
+         Copyright 14.04.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fitspheremz(double[,] xy, int npoints, int nx, out double[] cx, out double rlo, out double rhi)
+    {
+        cx = new double[0];
+        rlo = 0;
+        rhi = 0;
+        fitsphere.fitspheremz(xy, npoints, nx, ref cx, ref rlo, ref rhi, null);
+    }
+    
+    public static void fitspheremz(double[,] xy, int npoints, int nx, out double[] cx, out double rlo, out double rhi, alglib.xparams _params)
+    {
+        cx = new double[0];
+        rlo = 0;
+        rhi = 0;
+        fitsphere.fitspheremz(xy, npoints, nx, ref cx, ref rlo, ref rhi, _params);
+    }
+    
+    /*************************************************************************
+    Fitting minimum circumscribed, maximum inscribed or minimum  zone  circles
+    (or NX-dimensional spheres)  to  data  (a  set of points in NX-dimensional
+    space).
+
+    This  is  expert  function  which  allows  to  tweak  many  parameters  of
+    underlying nonlinear solver:
+    * stopping criteria for inner iterations
+    * number of outer iterations
+    * penalty coefficient used to handle  nonlinear  constraints  (we  convert
+      unconstrained nonsmooth optimization problem ivolving max() and/or min()
+      operations to quadratically constrained smooth one).
+
+    You may tweak all these parameters or only some  of  them,  leaving  other
+    ones at their default state - just specify zero  value,  and  solver  will
+    fill it with appropriate default one.
+
+    These comments also include some discussion of  approach  used  to  handle
+    such unusual fitting problem,  its  stability,  drawbacks  of  alternative
+    methods, and convergence properties.
+
+    INPUT PARAMETERS:
+        XY      -   array[NPoints,NX] (or larger), contains dataset.
+                    One row = one point in NX-dimensional space.
+        NPoints -   dataset size, NPoints>0
+        NX      -   space dimensionality, NX>0 (1, 2, 3, 4, 5 and so on)
+        ProblemType-used to encode problem type:
+                    * 0 for least squares circle
+                    * 1 for minimum circumscribed circle/sphere fitting (MC)
+                    * 2 for  maximum inscribed circle/sphere fitting (MI)
+                    * 3 for minimum zone circle fitting (difference between
+                        Rhi and Rlo is minimized), denoted as MZ
+        EpsX    -   stopping condition for NLC optimizer:
+                    * must be non-negative
+                    * use 0 to choose default value (1.0E-12 is used by default)
+                    * you may specify larger values, up to 1.0E-6, if you want
+                      to   speed-up   solver;   NLC   solver  performs several
+                      preconditioned  outer  iterations,   so   final   result
+                      typically has precision much better than EpsX.
+        AULIts  -   number of outer iterations performed by NLC optimizer:
+                    * must be non-negative
+                    * use 0 to choose default value (20 is used by default)
+                    * you may specify values smaller than 20 if you want to
+                      speed up solver; 10 often results in good combination of
+                      precision and speed; sometimes you may get good results
+                      with just 6 outer iterations.
+                    Ignored for ProblemType=0.
+        Penalty -   penalty coefficient for NLC optimizer:
+                    * must be non-negative
+                    * use 0 to choose default value (1.0E6 in current version)
+                    * it should be really large, 1.0E6...1.0E7 is a good value
+                      to start from;
+                    * generally, default value is good enough
+                    Ignored for ProblemType=0.
+
+    OUTPUT PARAMETERS:
+        CX      -   central point for a sphere
+        RLo     -   radius:
+                    * for ProblemType=2,3, radius of the inscribed sphere
+                    * for ProblemType=0 - radius of the least squares sphere
+                    * for ProblemType=1 - zero
+        RHo     -   radius:
+                    * for ProblemType=1,3, radius of the circumscribed sphere
+                    * for ProblemType=0 - radius of the least squares sphere
+                    * for ProblemType=2 - zero
+
+    NOTE: ON THE UNIQUENESS OF SOLUTIONS
+
+    ALGLIB provides solution to several related circle fitting  problems:   MC
+    (minimum circumscribed), MI (maximum inscribed)   and   MZ  (minimum zone)
+    fitting, LS (least squares) fitting.
+
+    It  is  important  to  note  that  among these problems only MC and LS are
+    convex and have unique solution independently from starting point.
+
+    As  for MI,  it  may (or  may  not, depending on dataset properties)  have
+    multiple solutions, and it always  has  one degenerate solution C=infinity
+    which corresponds to infinitely large radius. Thus, there are no guarantees
+    that solution to  MI returned by this solver will be the best one (and  no
+    one can provide you with such guarantee because problem is  NP-hard).  The
+    only guarantee you have is that this solution is locally optimal, i.e.  it
+    can not be improved by infinitesimally small tweaks in the parameters.
+
+    It  is  also  possible  to "run away" to infinity when  started  from  bad
+    initial point located outside of point cloud (or when point cloud does not
+    span entire circumference/surface of the sphere).
+
+    Finally,  MZ (minimum zone circle) stands somewhere between MC  and  MI in
+    stability. It is somewhat regularized by "circumscribed" term of the merit
+    function; however, solutions to  MZ may be non-unique, and in some unlucky
+    cases it is also possible to "run away to infinity".
+
+
+    NOTE: ON THE NONLINEARLY CONSTRAINED PROGRAMMING APPROACH
+
+    The problem formulation for MC  (minimum circumscribed   circle;  for  the
+    sake of simplicity we omit MZ and MI here) is:
+
+            [     [         ]2 ]
+        min [ max [ XY[i]-C ]  ]
+         C  [  i  [         ]  ]
+
+    i.e. it is unconstrained nonsmooth optimization problem of finding  "best"
+    central point, with radius R being unambiguously  determined  from  C.  In
+    order to move away from non-smoothness we use following reformulation:
+
+            [   ]                  [         ]2
+        min [ R ] subject to R>=0, [ XY[i]-C ]  <= R^2
+        C,R [   ]                  [         ]
+
+    i.e. it becomes smooth quadratically constrained optimization problem with
+    linear target function. Such problem statement is 100% equivalent  to  the
+    original nonsmooth one, but much easier  to  approach.  We solve  it  with
+    MinNLC solver provided by ALGLIB.
+
+
+    NOTE: ON INSTABILITY OF SEQUENTIAL LINEARIZATION APPROACH
+
+    ALGLIB  has  nonlinearly  constrained  solver which proved to be stable on
+    such problems. However, some authors proposed to linearize constraints  in
+    the vicinity of current approximation (Ci,Ri) and to get next  approximate
+    solution (Ci+1,Ri+1) as solution to linear programming problem. Obviously,
+    LP problems are easier than nonlinearly constrained ones.
+
+    Indeed,  such approach  to   MC/MI/MZ   resulted   in  ~10-20x increase in
+    performance (when compared with NLC solver). However, it turned  out  that
+    in some cases linearized model fails to predict correct direction for next
+    step and tells us that we converged to solution even when we are still 2-4
+    digits of precision away from it.
+
+    It is important that it is not failure of LP solver - it is failure of the
+    linear model;  even  when  solved  exactly,  it  fails  to  handle  subtle
+    nonlinearities which arise near the solution. We validated it by comparing
+    results returned by ALGLIB linear solver with that of MATLAB.
+
+    In our experiments with linearization:
+    * MC failed most often, at both realistic and synthetic datasets
+    * MI sometimes failed, but sometimes succeeded
+    * MZ often  succeeded; our guess is that presence of two independent  sets
+      of constraints (one set for Rlo and another one for Rhi) and  two  terms
+      in the target function (Rlo and Rhi) regularizes task,  so  when  linear
+      model fails to handle nonlinearities from Rlo, it uses  Rhi  as  a  hint
+      (and vice versa).
+
+    Because linearization approach failed to achieve stable results, we do not
+    include it in ALGLIB.
+
+
+      -- ALGLIB --
+         Copyright 14.04.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void fitspherex(double[,] xy, int npoints, int nx, int problemtype, double epsx, int aulits, double penalty, out double[] cx, out double rlo, out double rhi)
+    {
+        cx = new double[0];
+        rlo = 0;
+        rhi = 0;
+        fitsphere.fitspherex(xy, npoints, nx, problemtype, epsx, aulits, penalty, ref cx, ref rlo, ref rhi, null);
+    }
+    
+    public static void fitspherex(double[,] xy, int npoints, int nx, int problemtype, double epsx, int aulits, double penalty, out double[] cx, out double rlo, out double rhi, alglib.xparams _params)
+    {
+        cx = new double[0];
+        rlo = 0;
+        rhi = 0;
+        fitsphere.fitspherex(xy, npoints, nx, problemtype, epsx, aulits, penalty, ref cx, ref rlo, ref rhi, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    1-dimensional spline interpolant
+    *************************************************************************/
+    public class spline1dinterpolant : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public spline1dinterpolant()
+        {
+            _innerobj = new spline1d.spline1dinterpolant();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new spline1dinterpolant((spline1d.spline1dinterpolant)_innerobj.make_copy());
+        }
+    
+        //
+        // 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;
+        }
+    }
+
+
+    /*************************************************************************
+    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 : alglibobject
+    {
+        //
+        // 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 spline1d.spline1dfitreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new spline1dfitreport((spline1d.spline1dfitreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private spline1d.spline1dfitreport _innerobj;
+        public spline1d.spline1dfitreport innerobj { get { return _innerobj; } }
+        public spline1dfitreport(spline1d.spline1dfitreport 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, null);
+    }
+    
+    public static void spline1dbuildlinear(double[] x, double[] y, int n, out spline1dinterpolant c, alglib.xparams _params)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildlinear(x, y, n, c.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spline1dbuildlinear(double[] x, double[] y, out spline1dinterpolant c, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void spline1dbuildcubic(double[] x, double[] y, int n, int boundltype, double boundl, int boundrtype, double boundr, out spline1dinterpolant c, alglib.xparams _params)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildcubic(x, y, n, boundltype, boundl, boundrtype, boundr, c.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spline1dbuildcubic(double[] x, double[] y, out spline1dinterpolant c, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void spline1dgriddiffcubic(double[] x, double[] y, int n, int boundltype, double boundl, int boundrtype, double boundr, out double[] d, alglib.xparams _params)
+    {
+        d = new double[0];
+        spline1d.spline1dgriddiffcubic(x, y, n, boundltype, boundl, boundrtype, boundr, ref d, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spline1dgriddiffcubic(double[] x, double[] y, out double[] d, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void spline1dgriddiff2cubic(double[] x, double[] y, int n, int boundltype, double boundl, int boundrtype, double boundr, out double[] d1, out double[] d2, alglib.xparams _params)
+    {
+        d1 = new double[0];
+        d2 = new double[0];
+        spline1d.spline1dgriddiff2cubic(x, y, n, boundltype, boundl, boundrtype, boundr, ref d1, ref d2, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spline1dgriddiff2cubic(double[] x, double[] y, out double[] d1, out double[] d2, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        y2 = new double[0];
+        spline1d.spline1dconvcubic(x, y, n, boundltype, boundl, boundrtype, boundr, x2, n2, ref y2, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spline1dconvcubic(double[] x, double[] y, double[] x2, out double[] y2, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        y2 = new double[0];
+        d2 = new double[0];
+        spline1d.spline1dconvdiffcubic(x, y, n, boundltype, boundl, boundrtype, boundr, x2, n2, ref y2, ref d2, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spline1dconvdiffcubic(double[] x, double[] y, double[] x2, out double[] y2, out double[] d2, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        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, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spline1dconvdiff2cubic(double[] x, double[] y, double[] x2, out double[] y2, out double[] d2, out double[] dd2, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void spline1dbuildcatmullrom(double[] x, double[] y, int n, int boundtype, double tension, out spline1dinterpolant c, alglib.xparams _params)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildcatmullrom(x, y, n, boundtype, tension, c.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spline1dbuildcatmullrom(double[] x, double[] y, out spline1dinterpolant c, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void spline1dbuildhermite(double[] x, double[] y, double[] d, int n, out spline1dinterpolant c, alglib.xparams _params)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildhermite(x, y, d, n, c.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spline1dbuildhermite(double[] x, double[] y, double[] d, out spline1dinterpolant c, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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>=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 spline1dbuildakima(double[] x, double[] y, int n, out spline1dinterpolant c)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildakima(x, y, n, c.innerobj, null);
+    }
+    
+    public static void spline1dbuildakima(double[] x, double[] y, int n, out spline1dinterpolant c, alglib.xparams _params)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildakima(x, y, n, c.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spline1dbuildakima(double[] x, double[] y, out spline1dinterpolant c, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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)
+    {
+    
+        return spline1d.spline1dcalc(c.innerobj, x, null);
+    }
+    
+    public static double spline1dcalc(spline1dinterpolant c, double x, alglib.xparams _params)
+    {
+    
+        return spline1d.spline1dcalc(c.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void spline1ddiff(spline1dinterpolant c, double x, out double s, out double ds, out double d2s, alglib.xparams _params)
+    {
+        s = 0;
+        ds = 0;
+        d2s = 0;
+        spline1d.spline1ddiff(c.innerobj, x, ref s, ref ds, ref d2s, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine unpacks the spline into the coefficients table.
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+        X   -   point
+
+    OUTPUT PARAMETERS:
+        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]
+
+    NOTE:
+        You  can rebuild spline with  Spline1DBuildHermite()  function,  which
+        accepts as inputs function values and derivatives at nodes, which  are
+        easy to calculate when you have coefficients.
+
+      -- 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, null);
+    }
+    
+    public static void spline1dunpack(spline1dinterpolant c, out int n, out double[,] tbl, alglib.xparams _params)
+    {
+        n = 0;
+        tbl = new double[0,0];
+        spline1d.spline1dunpack(c.innerobj, ref n, ref tbl, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void spline1dlintransx(spline1dinterpolant c, double a, double b, alglib.xparams _params)
+    {
+    
+        spline1d.spline1dlintransx(c.innerobj, a, b, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void spline1dlintransy(spline1dinterpolant c, double a, double b, alglib.xparams _params)
+    {
+    
+        spline1d.spline1dlintransy(c.innerobj, a, b, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return spline1d.spline1dintegrate(c.innerobj, x, null);
+    }
+    
+    public static double spline1dintegrate(spline1dinterpolant c, double x, alglib.xparams _params)
+    {
+    
+        return spline1d.spline1dintegrate(c.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        X   -   points, array[0..N-1].
+        Y   -   function values, array[0..N-1].
+        N   -   number of points (optional):
+                * N>0
+                * if given, only first N elements of X/Y are processed
+                * if not given, automatically determined from X/Y 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 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();
+        spline1d.spline1dfitpenalized(x, y, n, m, rho, ref info, s.innerobj, rep.innerobj, null);
+    }
+    
+    public static void spline1dfitpenalized(double[] x, double[] y, int n, int m, double rho, out int info, out spline1dinterpolant s, out spline1dfitreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        spline1d.spline1dfitpenalized(x, y, n, m, rho, ref info, s.innerobj, rep.innerobj, _params);
+    }
+            
+    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);
+        spline1d.spline1dfitpenalized(x, y, n, m, rho, ref info, s.innerobj, rep.innerobj, null);
+    
+        return;
+    }
+            
+    public static void spline1dfitpenalized(double[] x, double[] y, int m, double rho, out int info, out spline1dinterpolant s, out spline1dfitreport rep, alglib.xparams _params)
+    {
+        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);
+        spline1d.spline1dfitpenalized(x, y, n, m, rho, ref info, s.innerobj, rep.innerobj, _params);
+    
+        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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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();
+        spline1d.spline1dfitpenalizedw(x, y, w, n, m, rho, ref info, s.innerobj, rep.innerobj, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        spline1d.spline1dfitpenalizedw(x, y, w, n, m, rho, ref info, s.innerobj, rep.innerobj, _params);
+    }
+            
+    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);
+        spline1d.spline1dfitpenalizedw(x, y, w, n, m, rho, ref info, s.innerobj, rep.innerobj, null);
+    
+        return;
+    }
+            
+    public static void spline1dfitpenalizedw(double[] x, double[] y, double[] w, int m, double rho, out int info, out spline1dinterpolant s, out spline1dfitreport rep, alglib.xparams _params)
+    {
+        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);
+        spline1d.spline1dfitpenalizedw(x, y, w, n, m, rho, ref info, s.innerobj, rep.innerobj, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function builds monotone cubic Hermite interpolant. This interpolant
+    is monotonic in [x(0),x(n-1)] and is constant outside of this interval.
+
+    In  case  y[]  form  non-monotonic  sequence,  interpolant  is  piecewise
+    monotonic.  Say, for x=(0,1,2,3,4)  and  y=(0,1,2,1,0)  interpolant  will
+    monotonically grow at [0..2] and monotonically decrease at [2..4].
+
+    INPUT PARAMETERS:
+        X           -   spline nodes, array[0..N-1]. Subroutine automatically
+                        sorts points, so caller may pass unsorted array.
+        Y           -   function values, array[0..N-1]
+        N           -   the number of points(N>=2).
+
+    OUTPUT PARAMETERS:
+        C           -   spline interpolant.
+
+     -- ALGLIB PROJECT --
+         Copyright 21.06.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline1dbuildmonotone(double[] x, double[] y, int n, out spline1dinterpolant c)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildmonotone(x, y, n, c.innerobj, null);
+    }
+    
+    public static void spline1dbuildmonotone(double[] x, double[] y, int n, out spline1dinterpolant c, alglib.xparams _params)
+    {
+        c = new spline1dinterpolant();
+        spline1d.spline1dbuildmonotone(x, y, n, c.innerobj, _params);
+    }
+            
+    public static void spline1dbuildmonotone(double[] x, double[] y, out spline1dinterpolant c)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dbuildmonotone': looks like one of arguments has wrong size");
+        c = new spline1dinterpolant();
+        n = ap.len(x);
+        spline1d.spline1dbuildmonotone(x, y, n, c.innerobj, null);
+    
+        return;
+    }
+            
+    public static void spline1dbuildmonotone(double[] x, double[] y, out spline1dinterpolant c, alglib.xparams _params)
+    {
+        int n;
+        if( (ap.len(x)!=ap.len(y)))
+            throw new alglibexception("Error while calling 'spline1dbuildmonotone': looks like one of arguments has wrong size");
+        c = new spline1dinterpolant();
+        n = ap.len(x);
+        spline1d.spline1dbuildmonotone(x, y, n, c.innerobj, _params);
+    
+        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 : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public pspline2interpolant()
+        {
+            _innerobj = new parametric.pspline2interpolant();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new pspline2interpolant((parametric.pspline2interpolant)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private parametric.pspline2interpolant _innerobj;
+        public parametric.pspline2interpolant innerobj { get { return _innerobj; } }
+        public pspline2interpolant(parametric.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 : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public pspline3interpolant()
+        {
+            _innerobj = new parametric.pspline3interpolant();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new pspline3interpolant((parametric.pspline3interpolant)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private parametric.pspline3interpolant _innerobj;
+        public parametric.pspline3interpolant innerobj { get { return _innerobj; } }
+        public pspline3interpolant(parametric.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();
+        parametric.pspline2build(xy, n, st, pt, p.innerobj, null);
+    }
+    
+    public static void pspline2build(double[,] xy, int n, int st, int pt, out pspline2interpolant p, alglib.xparams _params)
+    {
+        p = new pspline2interpolant();
+        parametric.pspline2build(xy, n, st, pt, p.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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();
+        parametric.pspline3build(xy, n, st, pt, p.innerobj, null);
+    }
+    
+    public static void pspline3build(double[,] xy, int n, int st, int pt, out pspline3interpolant p, alglib.xparams _params)
+    {
+        p = new pspline3interpolant();
+        parametric.pspline3build(xy, n, st, pt, p.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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();
+        parametric.pspline2buildperiodic(xy, n, st, pt, p.innerobj, null);
+    }
+    
+    public static void pspline2buildperiodic(double[,] xy, int n, int st, int pt, out pspline2interpolant p, alglib.xparams _params)
+    {
+        p = new pspline2interpolant();
+        parametric.pspline2buildperiodic(xy, n, st, pt, p.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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();
+        parametric.pspline3buildperiodic(xy, n, st, pt, p.innerobj, null);
+    }
+    
+    public static void pspline3buildperiodic(double[,] xy, int n, int st, int pt, out pspline3interpolant p, alglib.xparams _params)
+    {
+        p = new pspline3interpolant();
+        parametric.pspline3buildperiodic(xy, n, st, pt, p.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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];
+        parametric.pspline2parametervalues(p.innerobj, ref n, ref t, null);
+    }
+    
+    public static void pspline2parametervalues(pspline2interpolant p, out int n, out double[] t, alglib.xparams _params)
+    {
+        n = 0;
+        t = new double[0];
+        parametric.pspline2parametervalues(p.innerobj, ref n, ref t, _params);
+    }
+    
+    /*************************************************************************
+    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];
+        parametric.pspline3parametervalues(p.innerobj, ref n, ref t, null);
+    }
+    
+    public static void pspline3parametervalues(pspline3interpolant p, out int n, out double[] t, alglib.xparams _params)
+    {
+        n = 0;
+        t = new double[0];
+        parametric.pspline3parametervalues(p.innerobj, ref n, ref t, _params);
+    }
+    
+    /*************************************************************************
+    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;
+        parametric.pspline2calc(p.innerobj, t, ref x, ref y, null);
+    }
+    
+    public static void pspline2calc(pspline2interpolant p, double t, out double x, out double y, alglib.xparams _params)
+    {
+        x = 0;
+        y = 0;
+        parametric.pspline2calc(p.innerobj, t, ref x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    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;
+        parametric.pspline3calc(p.innerobj, t, ref x, ref y, ref z, null);
+    }
+    
+    public static void pspline3calc(pspline3interpolant p, double t, out double x, out double y, out double z, alglib.xparams _params)
+    {
+        x = 0;
+        y = 0;
+        z = 0;
+        parametric.pspline3calc(p.innerobj, t, ref x, ref y, ref z, _params);
+    }
+    
+    /*************************************************************************
+    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;
+        parametric.pspline2tangent(p.innerobj, t, ref x, ref y, null);
+    }
+    
+    public static void pspline2tangent(pspline2interpolant p, double t, out double x, out double y, alglib.xparams _params)
+    {
+        x = 0;
+        y = 0;
+        parametric.pspline2tangent(p.innerobj, t, ref x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    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;
+        parametric.pspline3tangent(p.innerobj, t, ref x, ref y, ref z, null);
+    }
+    
+    public static void pspline3tangent(pspline3interpolant p, double t, out double x, out double y, out double z, alglib.xparams _params)
+    {
+        x = 0;
+        y = 0;
+        z = 0;
+        parametric.pspline3tangent(p.innerobj, t, ref x, ref y, ref z, _params);
+    }
+    
+    /*************************************************************************
+    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;
+        parametric.pspline2diff(p.innerobj, t, ref x, ref dx, ref y, ref dy, null);
+    }
+    
+    public static void pspline2diff(pspline2interpolant p, double t, out double x, out double dx, out double y, out double dy, alglib.xparams _params)
+    {
+        x = 0;
+        dx = 0;
+        y = 0;
+        dy = 0;
+        parametric.pspline2diff(p.innerobj, t, ref x, ref dx, ref y, ref dy, _params);
+    }
+    
+    /*************************************************************************
+    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;
+        parametric.pspline3diff(p.innerobj, t, ref x, ref dx, ref y, ref dy, ref z, ref dz, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        x = 0;
+        dx = 0;
+        y = 0;
+        dy = 0;
+        z = 0;
+        dz = 0;
+        parametric.pspline3diff(p.innerobj, t, ref x, ref dx, ref y, ref dy, ref z, ref dz, _params);
+    }
+    
+    /*************************************************************************
+    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;
+        parametric.pspline2diff2(p.innerobj, t, ref x, ref dx, ref d2x, ref y, ref dy, ref d2y, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        x = 0;
+        dx = 0;
+        d2x = 0;
+        y = 0;
+        dy = 0;
+        d2y = 0;
+        parametric.pspline2diff2(p.innerobj, t, ref x, ref dx, ref d2x, ref y, ref dy, ref d2y, _params);
+    }
+    
+    /*************************************************************************
+    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;
+        parametric.pspline3diff2(p.innerobj, t, ref x, ref dx, ref d2x, ref y, ref dy, ref d2y, ref z, ref dz, ref d2z, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        x = 0;
+        dx = 0;
+        d2x = 0;
+        y = 0;
+        dy = 0;
+        d2y = 0;
+        z = 0;
+        dz = 0;
+        d2z = 0;
+        parametric.pspline3diff2(p.innerobj, t, ref x, ref dx, ref d2x, ref y, ref dy, ref d2y, ref z, ref dz, ref d2z, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return parametric.pspline2arclength(p.innerobj, a, b, null);
+    }
+    
+    public static double pspline2arclength(pspline2interpolant p, double a, double b, alglib.xparams _params)
+    {
+    
+        return parametric.pspline2arclength(p.innerobj, a, b, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return parametric.pspline3arclength(p.innerobj, a, b, null);
+    }
+    
+    public static double pspline3arclength(pspline3interpolant p, double a, double b, alglib.xparams _params)
+    {
+    
+        return parametric.pspline3arclength(p.innerobj, a, b, _params);
+    }
+    
+    /*************************************************************************
+    This  subroutine fits piecewise linear curve to points with Ramer-Douglas-
+    Peucker algorithm. This  function  performs PARAMETRIC fit, i.e. it can be
+    used to fit curves like circles.
+
+    On  input  it  accepts dataset which describes parametric multidimensional
+    curve X(t), with X being vector, and t taking values in [0,N), where N  is
+    a number of points in dataset. As result, it returns reduced  dataset  X2,
+    which can be used to build  parametric  curve  X2(t),  which  approximates
+    X(t) with desired precision (or has specified number of sections).
+
+
+    INPUT PARAMETERS:
+        X       -   array of multidimensional points:
+                    * at least N elements, leading N elements are used if more
+                      than N elements were specified
+                    * order of points is IMPORTANT because  it  is  parametric
+                      fit
+                    * each row of array is one point which has D coordinates
+        N       -   number of elements in X
+        D       -   number of dimensions (elements per row of X)
+        StopM   -   stopping condition - desired number of sections:
+                    * at most M sections are generated by this function
+                    * less than M sections can be generated if we have N<M
+                      (or some X are non-distinct).
+                    * zero StopM means that algorithm does not stop after
+                      achieving some pre-specified section count
+        StopEps -   stopping condition - desired precision:
+                    * algorithm stops after error in each section is at most Eps
+                    * zero Eps means that algorithm does not stop after
+                      achieving some pre-specified precision
+
+    OUTPUT PARAMETERS:
+        X2      -   array of corner points for piecewise approximation,
+                    has length NSections+1 or zero (for NSections=0).
+        Idx2    -   array of indexes (parameter values):
+                    * has length NSections+1 or zero (for NSections=0).
+                    * each element of Idx2 corresponds to same-numbered
+                      element of X2
+                    * each element of Idx2 is index of  corresponding  element
+                      of X2 at original array X, i.e. I-th  row  of  X2  is
+                      Idx2[I]-th row of X.
+                    * elements of Idx2 can be treated as parameter values
+                      which should be used when building new parametric curve
+                    * Idx2[0]=0, Idx2[NSections]=N-1
+        NSections-  number of sections found by algorithm, NSections<=M,
+                    NSections can be zero for degenerate datasets
+                    (N<=1 or all X[] are non-distinct).
+
+    NOTE: algorithm stops after:
+          a) dividing curve into StopM sections
+          b) achieving required precision StopEps
+          c) dividing curve into N-1 sections
+          If both StopM and StopEps are non-zero, algorithm is stopped by  the
+          FIRST criterion which is satisfied. In case both StopM  and  StopEps
+          are zero, algorithm stops because of (c).
+
+      -- ALGLIB --
+         Copyright 02.10.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void parametricrdpfixed(double[,] x, int n, int d, int stopm, double stopeps, out double[,] x2, out int[] idx2, out int nsections)
+    {
+        x2 = new double[0,0];
+        idx2 = new int[0];
+        nsections = 0;
+        parametric.parametricrdpfixed(x, n, d, stopm, stopeps, ref x2, ref idx2, ref nsections, null);
+    }
+    
+    public static void parametricrdpfixed(double[,] x, int n, int d, int stopm, double stopeps, out double[,] x2, out int[] idx2, out int nsections, alglib.xparams _params)
+    {
+        x2 = new double[0,0];
+        idx2 = new int[0];
+        nsections = 0;
+        parametric.parametricrdpfixed(x, n, d, stopm, stopeps, ref x2, ref idx2, ref nsections, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    3-dimensional spline inteprolant
+    *************************************************************************/
+    public class spline3dinterpolant : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public spline3dinterpolant()
+        {
+            _innerobj = new spline3d.spline3dinterpolant();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new spline3dinterpolant((spline3d.spline3dinterpolant)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private spline3d.spline3dinterpolant _innerobj;
+        public spline3d.spline3dinterpolant innerobj { get { return _innerobj; } }
+        public spline3dinterpolant(spline3d.spline3dinterpolant obj)
+        {
+            _innerobj = obj;
+        }
+    }
+    
+    /*************************************************************************
+    This subroutine calculates the value of the trilinear or tricubic spline at
+    the given point (X,Y,Z).
+
+    INPUT PARAMETERS:
+        C   -   coefficients table.
+                Built by BuildBilinearSpline or BuildBicubicSpline.
+        X, Y,
+        Z   -   point
+
+    Result:
+        S(x,y,z)
+
+      -- ALGLIB PROJECT --
+         Copyright 26.04.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static double spline3dcalc(spline3dinterpolant c, double x, double y, double z)
+    {
+    
+        return spline3d.spline3dcalc(c.innerobj, x, y, z, null);
+    }
+    
+    public static double spline3dcalc(spline3dinterpolant c, double x, double y, double z, alglib.xparams _params)
+    {
+    
+        return spline3d.spline3dcalc(c.innerobj, x, y, z, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine performs linear transformation of the spline argument.
+
+    INPUT PARAMETERS:
+        C       -   spline interpolant
+        AX, BX  -   transformation coefficients: x = A*u + B
+        AY, BY  -   transformation coefficients: y = A*v + B
+        AZ, BZ  -   transformation coefficients: z = A*w + B
+
+    OUTPUT PARAMETERS:
+        C   -   transformed spline
+
+      -- ALGLIB PROJECT --
+         Copyright 26.04.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline3dlintransxyz(spline3dinterpolant c, double ax, double bx, double ay, double by, double az, double bz)
+    {
+    
+        spline3d.spline3dlintransxyz(c.innerobj, ax, bx, ay, by, az, bz, null);
+    }
+    
+    public static void spline3dlintransxyz(spline3dinterpolant c, double ax, double bx, double ay, double by, double az, double bz, alglib.xparams _params)
+    {
+    
+        spline3d.spline3dlintransxyz(c.innerobj, ax, bx, ay, by, az, bz, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine performs linear transformation of the spline.
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+        A, B-   transformation coefficients: S2(x,y) = A*S(x,y,z) + B
+
+    OUTPUT PARAMETERS:
+        C   -   transformed spline
+
+      -- ALGLIB PROJECT --
+         Copyright 26.04.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline3dlintransf(spline3dinterpolant c, double a, double b)
+    {
+    
+        spline3d.spline3dlintransf(c.innerobj, a, b, null);
+    }
+    
+    public static void spline3dlintransf(spline3dinterpolant c, double a, double b, alglib.xparams _params)
+    {
+    
+        spline3d.spline3dlintransf(c.innerobj, a, b, _params);
+    }
+    
+    /*************************************************************************
+    Trilinear spline resampling
+
+    INPUT PARAMETERS:
+        A           -   array[0..OldXCount*OldYCount*OldZCount-1], function
+                        values at the old grid, :
+                            A[0]        x=0,y=0,z=0
+                            A[1]        x=1,y=0,z=0
+                            A[..]       ...
+                            A[..]       x=oldxcount-1,y=0,z=0
+                            A[..]       x=0,y=1,z=0
+                            A[..]       ...
+                            ...
+        OldZCount   -   old Z-count, OldZCount>1
+        OldYCount   -   old Y-count, OldYCount>1
+        OldXCount   -   old X-count, OldXCount>1
+        NewZCount   -   new Z-count, NewZCount>1
+        NewYCount   -   new Y-count, NewYCount>1
+        NewXCount   -   new X-count, NewXCount>1
+
+    OUTPUT PARAMETERS:
+        B           -   array[0..NewXCount*NewYCount*NewZCount-1], function
+                        values at the new grid:
+                            B[0]        x=0,y=0,z=0
+                            B[1]        x=1,y=0,z=0
+                            B[..]       ...
+                            B[..]       x=newxcount-1,y=0,z=0
+                            B[..]       x=0,y=1,z=0
+                            B[..]       ...
+                            ...
+
+      -- ALGLIB routine --
+         26.04.2012
+         Copyright by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline3dresampletrilinear(double[] a, int oldzcount, int oldycount, int oldxcount, int newzcount, int newycount, int newxcount, out double[] b)
+    {
+        b = new double[0];
+        spline3d.spline3dresampletrilinear(a, oldzcount, oldycount, oldxcount, newzcount, newycount, newxcount, ref b, null);
+    }
+    
+    public static void spline3dresampletrilinear(double[] a, int oldzcount, int oldycount, int oldxcount, int newzcount, int newycount, int newxcount, out double[] b, alglib.xparams _params)
+    {
+        b = new double[0];
+        spline3d.spline3dresampletrilinear(a, oldzcount, oldycount, oldxcount, newzcount, newycount, newxcount, ref b, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine builds trilinear vector-valued spline.
+
+    INPUT PARAMETERS:
+        X   -   spline abscissas,  array[0..N-1]
+        Y   -   spline ordinates,  array[0..M-1]
+        Z   -   spline applicates, array[0..L-1]
+        F   -   function values, array[0..M*N*L*D-1]:
+                * first D elements store D values at (X[0],Y[0],Z[0])
+                * next D elements store D values at (X[1],Y[0],Z[0])
+                * next D elements store D values at (X[2],Y[0],Z[0])
+                * ...
+                * next D elements store D values at (X[0],Y[1],Z[0])
+                * next D elements store D values at (X[1],Y[1],Z[0])
+                * next D elements store D values at (X[2],Y[1],Z[0])
+                * ...
+                * next D elements store D values at (X[0],Y[0],Z[1])
+                * next D elements store D values at (X[1],Y[0],Z[1])
+                * next D elements store D values at (X[2],Y[0],Z[1])
+                * ...
+                * general form - D function values at (X[i],Y[j]) are stored
+                  at F[D*(N*(M*K+J)+I)...D*(N*(M*K+J)+I)+D-1].
+        M,N,
+        L   -   grid size, M>=2, N>=2, L>=2
+        D   -   vector dimension, D>=1
+
+    OUTPUT PARAMETERS:
+        C   -   spline interpolant
+
+      -- ALGLIB PROJECT --
+         Copyright 26.04.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline3dbuildtrilinearv(double[] x, int n, double[] y, int m, double[] z, int l, double[] f, int d, out spline3dinterpolant c)
+    {
+        c = new spline3dinterpolant();
+        spline3d.spline3dbuildtrilinearv(x, n, y, m, z, l, f, d, c.innerobj, null);
+    }
+    
+    public static void spline3dbuildtrilinearv(double[] x, int n, double[] y, int m, double[] z, int l, double[] f, int d, out spline3dinterpolant c, alglib.xparams _params)
+    {
+        c = new spline3dinterpolant();
+        spline3d.spline3dbuildtrilinearv(x, n, y, m, z, l, f, d, c.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine calculates bilinear or bicubic vector-valued spline at the
+    given point (X,Y,Z).
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+        X, Y,
+        Z   -   point
+        F   -   output buffer, possibly preallocated array. In case array size
+                is large enough to store result, it is not reallocated.  Array
+                which is too short will be reallocated
+
+    OUTPUT PARAMETERS:
+        F   -   array[D] (or larger) which stores function values
+
+      -- ALGLIB PROJECT --
+         Copyright 26.04.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline3dcalcvbuf(spline3dinterpolant c, double x, double y, double z, ref double[] f)
+    {
+    
+        spline3d.spline3dcalcvbuf(c.innerobj, x, y, z, ref f, null);
+    }
+    
+    public static void spline3dcalcvbuf(spline3dinterpolant c, double x, double y, double z, ref double[] f, alglib.xparams _params)
+    {
+    
+        spline3d.spline3dcalcvbuf(c.innerobj, x, y, z, ref f, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine calculates trilinear or tricubic vector-valued spline at the
+    given point (X,Y,Z).
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+        X, Y,
+        Z   -   point
+
+    OUTPUT PARAMETERS:
+        F   -   array[D] which stores function values.  F is out-parameter and
+                it  is  reallocated  after  call to this function. In case you
+                want  to    reuse  previously  allocated  F,   you   may   use
+                Spline2DCalcVBuf(),  which  reallocates  F only when it is too
+                small.
+
+      -- ALGLIB PROJECT --
+         Copyright 26.04.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline3dcalcv(spline3dinterpolant c, double x, double y, double z, out double[] f)
+    {
+        f = new double[0];
+        spline3d.spline3dcalcv(c.innerobj, x, y, z, ref f, null);
+    }
+    
+    public static void spline3dcalcv(spline3dinterpolant c, double x, double y, double z, out double[] f, alglib.xparams _params)
+    {
+        f = new double[0];
+        spline3d.spline3dcalcv(c.innerobj, x, y, z, ref f, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine unpacks tri-dimensional spline into the coefficients table
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+
+    Result:
+        N   -   grid size (X)
+        M   -   grid size (Y)
+        L   -   grid size (Z)
+        D   -   number of components
+        SType-  spline type. Currently, only one spline type is supported:
+                trilinear spline, as indicated by SType=1.
+        Tbl -   spline coefficients: [0..(N-1)*(M-1)*(L-1)*D-1, 0..13].
+                For T=0..D-1 (component index), I = 0...N-2 (x index),
+                J=0..M-2 (y index), K=0..L-2 (z index):
+                    Q := T + I*D + J*D*(N-1) + K*D*(N-1)*(M-1),
+
+                    Q-th row stores decomposition for T-th component of the
+                    vector-valued function
+
+                    Tbl[Q,0] = X[i]
+                    Tbl[Q,1] = X[i+1]
+                    Tbl[Q,2] = Y[j]
+                    Tbl[Q,3] = Y[j+1]
+                    Tbl[Q,4] = Z[k]
+                    Tbl[Q,5] = Z[k+1]
+
+                    Tbl[Q,6] = C000
+                    Tbl[Q,7] = C100
+                    Tbl[Q,8] = C010
+                    Tbl[Q,9] = C110
+                    Tbl[Q,10]= C001
+                    Tbl[Q,11]= C101
+                    Tbl[Q,12]= C011
+                    Tbl[Q,13]= C111
+                On each grid square spline is equals to:
+                    S(x) = SUM(c[i,j,k]*(x^i)*(y^j)*(z^k), i=0..1, j=0..1, k=0..1)
+                    t = x-x[j]
+                    u = y-y[i]
+                    v = z-z[k]
+
+                NOTE: format of Tbl is given for SType=1. Future versions of
+                      ALGLIB can use different formats for different values of
+                      SType.
+
+      -- ALGLIB PROJECT --
+         Copyright 26.04.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline3dunpackv(spline3dinterpolant c, out int n, out int m, out int l, out int d, out int stype, out double[,] tbl)
+    {
+        n = 0;
+        m = 0;
+        l = 0;
+        d = 0;
+        stype = 0;
+        tbl = new double[0,0];
+        spline3d.spline3dunpackv(c.innerobj, ref n, ref m, ref l, ref d, ref stype, ref tbl, null);
+    }
+    
+    public static void spline3dunpackv(spline3dinterpolant c, out int n, out int m, out int l, out int d, out int stype, out double[,] tbl, alglib.xparams _params)
+    {
+        n = 0;
+        m = 0;
+        l = 0;
+        d = 0;
+        stype = 0;
+        tbl = new double[0,0];
+        spline3d.spline3dunpackv(c.innerobj, ref n, ref m, ref l, ref d, ref stype, ref tbl, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void polynomialbar2cheb(barycentricinterpolant p, double a, double b, out double[] t, alglib.xparams _params)
+    {
+        t = new double[0];
+        polint.polynomialbar2cheb(p.innerobj, a, b, ref t, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void polynomialcheb2bar(double[] t, int n, double a, double b, out barycentricinterpolant p, alglib.xparams _params)
+    {
+        p = new barycentricinterpolant();
+        polint.polynomialcheb2bar(t, n, a, b, p.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void polynomialcheb2bar(double[] t, double a, double b, out barycentricinterpolant p, alglib.xparams _params)
+    {
+        int n;
+    
+        p = new barycentricinterpolant();
+        n = ap.len(t);
+        polint.polynomialcheb2bar(t, n, a, b, p.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void polynomialbar2pow(barycentricinterpolant p, double c, double s, out double[] a, alglib.xparams _params)
+    {
+        a = new double[0];
+        polint.polynomialbar2pow(p.innerobj, c, s, ref a, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void polynomialbar2pow(barycentricinterpolant p, out double[] a, alglib.xparams _params)
+    {
+        double c;
+        double s;
+    
+        a = new double[0];
+        c = 0;
+        s = 1;
+        polint.polynomialbar2pow(p.innerobj, c, s, ref a, _params);
+    
+        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, null);
+    }
+    
+    public static void polynomialpow2bar(double[] a, int n, double c, double s, out barycentricinterpolant p, alglib.xparams _params)
+    {
+        p = new barycentricinterpolant();
+        polint.polynomialpow2bar(a, n, c, s, p.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void polynomialpow2bar(double[] a, out barycentricinterpolant p, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void polynomialbuild(double[] x, double[] y, int n, out barycentricinterpolant p, alglib.xparams _params)
+    {
+        p = new barycentricinterpolant();
+        polint.polynomialbuild(x, y, n, p.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void polynomialbuild(double[] x, double[] y, out barycentricinterpolant p, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void polynomialbuildeqdist(double a, double b, double[] y, int n, out barycentricinterpolant p, alglib.xparams _params)
+    {
+        p = new barycentricinterpolant();
+        polint.polynomialbuildeqdist(a, b, y, n, p.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void polynomialbuildeqdist(double a, double b, double[] y, out barycentricinterpolant p, alglib.xparams _params)
+    {
+        int n;
+    
+        p = new barycentricinterpolant();
+        n = ap.len(y);
+        polint.polynomialbuildeqdist(a, b, y, n, p.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void polynomialbuildcheb1(double a, double b, double[] y, int n, out barycentricinterpolant p, alglib.xparams _params)
+    {
+        p = new barycentricinterpolant();
+        polint.polynomialbuildcheb1(a, b, y, n, p.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void polynomialbuildcheb1(double a, double b, double[] y, out barycentricinterpolant p, alglib.xparams _params)
+    {
+        int n;
+    
+        p = new barycentricinterpolant();
+        n = ap.len(y);
+        polint.polynomialbuildcheb1(a, b, y, n, p.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void polynomialbuildcheb2(double a, double b, double[] y, int n, out barycentricinterpolant p, alglib.xparams _params)
+    {
+        p = new barycentricinterpolant();
+        polint.polynomialbuildcheb2(a, b, y, n, p.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void polynomialbuildcheb2(double a, double b, double[] y, out barycentricinterpolant p, alglib.xparams _params)
+    {
+        int n;
+    
+        p = new barycentricinterpolant();
+        n = ap.len(y);
+        polint.polynomialbuildcheb2(a, b, y, n, p.innerobj, _params);
+    
+        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)
+    {
+    
+        return polint.polynomialcalceqdist(a, b, f, n, t, null);
+    }
+    
+    public static double polynomialcalceqdist(double a, double b, double[] f, int n, double t, alglib.xparams _params)
+    {
+    
+        return polint.polynomialcalceqdist(a, b, f, n, t, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static double polynomialcalceqdist(double a, double b, double[] f, double t, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(f);
+        double result = polint.polynomialcalceqdist(a, b, f, n, t, _params);
+    
+        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)
+    {
+    
+        return polint.polynomialcalccheb1(a, b, f, n, t, null);
+    }
+    
+    public static double polynomialcalccheb1(double a, double b, double[] f, int n, double t, alglib.xparams _params)
+    {
+    
+        return polint.polynomialcalccheb1(a, b, f, n, t, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static double polynomialcalccheb1(double a, double b, double[] f, double t, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(f);
+        double result = polint.polynomialcalccheb1(a, b, f, n, t, _params);
+    
+        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)
+    {
+    
+        return polint.polynomialcalccheb2(a, b, f, n, t, null);
+    }
+    
+    public static double polynomialcalccheb2(double a, double b, double[] f, int n, double t, alglib.xparams _params)
+    {
+    
+        return polint.polynomialcalccheb2(a, b, f, n, t, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static double polynomialcalccheb2(double a, double b, double[] f, double t, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(f);
+        double result = polint.polynomialcalccheb2(a, b, f, n, t, _params);
+    
+        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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new polynomialfitreport((lsfit.polynomialfitreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new barycentricfitreport((lsfit.barycentricfitreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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;
+        }
+    }
+
+
+    /*************************************************************************
+    Least squares fitting report. This structure contains informational fields
+    which are set by fitting functions provided by this unit.
+
+    Different functions initialize different sets of  fields,  so  you  should
+    read documentation on specific function you used in order  to  know  which
+    fields are initialized.
+
+        TaskRCond       reciprocal of task's condition number
+        IterationsCount number of internal iterations
+
+        VarIdx          if user-supplied gradient contains errors  which  were
+                        detected by nonlinear fitter, this  field  is  set  to
+                        index  of  the  first  component  of gradient which is
+                        suspected to be spoiled by bugs.
+
+        RMSError        RMS error
+        AvgError        average error
+        AvgRelError     average relative error (for non-zero Y[I])
+        MaxError        maximum error
+
+        WRMSError       weighted RMS error
+
+        CovPar          covariance matrix for parameters, filled by some solvers
+        ErrPar          vector of errors in parameters, filled by some solvers
+        ErrCurve        vector of fit errors -  variability  of  the  best-fit
+                        curve, filled by some solvers.
+        Noise           vector of per-point noise estimates, filled by
+                        some solvers.
+        R2              coefficient of determination (non-weighted, non-adjusted),
+                        filled by some solvers.
+    *************************************************************************/
+    public class lsfitreport : alglibobject
+    {
+        //
+        // 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 int varidx { get { return _innerobj.varidx; } set { _innerobj.varidx = 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 double[,] covpar { get { return _innerobj.covpar; } set { _innerobj.covpar = value; } }
+        public double[] errpar { get { return _innerobj.errpar; } set { _innerobj.errpar = value; } }
+        public double[] errcurve { get { return _innerobj.errcurve; } set { _innerobj.errcurve = value; } }
+        public double[] noise { get { return _innerobj.noise; } set { _innerobj.noise = value; } }
+        public double r2 { get { return _innerobj.r2; } set { _innerobj.r2 = value; } }
+    
+        public lsfitreport()
+        {
+            _innerobj = new lsfit.lsfitreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new lsfitreport((lsfit.lsfitreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new lsfitstate((lsfit.lsfitstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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;
+        }
+    }
+    
+    /*************************************************************************
+    This  subroutine fits piecewise linear curve to points with Ramer-Douglas-
+    Peucker algorithm, which stops after generating specified number of linear
+    sections.
+
+    IMPORTANT:
+    * it does NOT perform least-squares fitting; it  builds  curve,  but  this
+      curve does not minimize some least squares metric.  See  description  of
+      RDP algorithm (say, in Wikipedia) for more details on WHAT is performed.
+    * this function does NOT work with parametric curves  (i.e.  curves  which
+      can be represented as {X(t),Y(t)}. It works with curves   which  can  be
+      represented as Y(X). Thus,  it  is  impossible  to  model  figures  like
+      circles  with  this  functions.
+      If  you  want  to  work  with  parametric   curves,   you   should   use
+      ParametricRDPFixed() function provided  by  "Parametric"  subpackage  of
+      "Interpolation" package.
+
+    INPUT PARAMETERS:
+        X       -   array of X-coordinates:
+                    * at least N elements
+                    * can be unordered (points are automatically sorted)
+                    * this function may accept non-distinct X (see below for
+                      more information on handling of such inputs)
+        Y       -   array of Y-coordinates:
+                    * at least N elements
+        N       -   number of elements in X/Y
+        M       -   desired number of sections:
+                    * at most M sections are generated by this function
+                    * less than M sections can be generated if we have N<M
+                      (or some X are non-distinct).
+
+    OUTPUT PARAMETERS:
+        X2      -   X-values of corner points for piecewise approximation,
+                    has length NSections+1 or zero (for NSections=0).
+        Y2      -   Y-values of corner points,
+                    has length NSections+1 or zero (for NSections=0).
+        NSections-  number of sections found by algorithm, NSections<=M,
+                    NSections can be zero for degenerate datasets
+                    (N<=1 or all X[] are non-distinct).
+
+    NOTE: X2/Y2 are ordered arrays, i.e. (X2[0],Y2[0]) is  a  first  point  of
+          curve, (X2[NSection-1],Y2[NSection-1]) is the last point.
+
+      -- ALGLIB --
+         Copyright 02.10.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lstfitpiecewiselinearrdpfixed(double[] x, double[] y, int n, int m, out double[] x2, out double[] y2, out int nsections)
+    {
+        x2 = new double[0];
+        y2 = new double[0];
+        nsections = 0;
+        lsfit.lstfitpiecewiselinearrdpfixed(x, y, n, m, ref x2, ref y2, ref nsections, null);
+    }
+    
+    public static void lstfitpiecewiselinearrdpfixed(double[] x, double[] y, int n, int m, out double[] x2, out double[] y2, out int nsections, alglib.xparams _params)
+    {
+        x2 = new double[0];
+        y2 = new double[0];
+        nsections = 0;
+        lsfit.lstfitpiecewiselinearrdpfixed(x, y, n, m, ref x2, ref y2, ref nsections, _params);
+    }
+    
+    /*************************************************************************
+    This  subroutine fits piecewise linear curve to points with Ramer-Douglas-
+    Peucker algorithm, which stops after achieving desired precision.
+
+    IMPORTANT:
+    * it performs non-least-squares fitting; it builds curve, but  this  curve
+      does not minimize some least squares  metric.  See  description  of  RDP
+      algorithm (say, in Wikipedia) for more details on WHAT is performed.
+    * this function does NOT work with parametric curves  (i.e.  curves  which
+      can be represented as {X(t),Y(t)}. It works with curves   which  can  be
+      represented as Y(X). Thus, it is impossible to model figures like circles
+      with this functions.
+      If  you  want  to  work  with  parametric   curves,   you   should   use
+      ParametricRDPFixed() function provided  by  "Parametric"  subpackage  of
+      "Interpolation" package.
+
+    INPUT PARAMETERS:
+        X       -   array of X-coordinates:
+                    * at least N elements
+                    * can be unordered (points are automatically sorted)
+                    * this function may accept non-distinct X (see below for
+                      more information on handling of such inputs)
+        Y       -   array of Y-coordinates:
+                    * at least N elements
+        N       -   number of elements in X/Y
+        Eps     -   positive number, desired precision.
+
+
+    OUTPUT PARAMETERS:
+        X2      -   X-values of corner points for piecewise approximation,
+                    has length NSections+1 or zero (for NSections=0).
+        Y2      -   Y-values of corner points,
+                    has length NSections+1 or zero (for NSections=0).
+        NSections-  number of sections found by algorithm,
+                    NSections can be zero for degenerate datasets
+                    (N<=1 or all X[] are non-distinct).
+
+    NOTE: X2/Y2 are ordered arrays, i.e. (X2[0],Y2[0]) is  a  first  point  of
+          curve, (X2[NSection-1],Y2[NSection-1]) is the last point.
+
+      -- ALGLIB --
+         Copyright 02.10.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lstfitpiecewiselinearrdp(double[] x, double[] y, int n, double eps, out double[] x2, out double[] y2, out int nsections)
+    {
+        x2 = new double[0];
+        y2 = new double[0];
+        nsections = 0;
+        lsfit.lstfitpiecewiselinearrdp(x, y, n, eps, ref x2, ref y2, ref nsections, null);
+    }
+    
+    public static void lstfitpiecewiselinearrdp(double[] x, double[] y, int n, double eps, out double[] x2, out double[] y2, out int nsections, alglib.xparams _params)
+    {
+        x2 = new double[0];
+        y2 = new double[0];
+        nsections = 0;
+        lsfit.lstfitpiecewiselinearrdp(x, y, n, eps, ref x2, ref y2, ref nsections, _params);
+    }
+    
+    /*************************************************************************
+    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()
+
+    NOTES:
+        you can convert P from barycentric form  to  the  power  or  Chebyshev
+        basis with PolynomialBar2Pow() or PolynomialBar2Cheb() functions  from
+        POLINT subpackage.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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
+
+      -- 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, null);
+    }
+    
+    public static void polynomialfit(double[] x, double[] y, int n, int m, out int info, out barycentricinterpolant p, out polynomialfitreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        p = new barycentricinterpolant();
+        rep = new polynomialfitreport();
+        lsfit.polynomialfit(x, y, n, m, ref info, p.innerobj, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void polynomialfit(double[] x, double[] y, int m, out int info, out barycentricinterpolant p, out polynomialfitreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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()
+
+    NOTES:
+        you can convert P from barycentric form  to  the  power  or  Chebyshev
+        basis with PolynomialBar2Pow() or PolynomialBar2Cheb() functions  from
+        POLINT subpackage.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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.
+
+    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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        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, _params);
+    }
+            
+    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, null);
+    
+        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, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function calculates value of four-parameter logistic (4PL)  model  at
+    specified point X. 4PL model has following form:
+
+        F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B))
+
+    INPUT PARAMETERS:
+        X       -   current point, X>=0:
+                    * zero X is correctly handled even for B<=0
+                    * negative X results in exception.
+        A, B, C, D- parameters of 4PL model:
+                    * A is unconstrained
+                    * B is unconstrained; zero or negative values are handled
+                      correctly.
+                    * C>0, non-positive value results in exception
+                    * D is unconstrained
+
+    RESULT:
+        model value at X
+
+    NOTE: if B=0, denominator is assumed to be equal to 2.0 even  for  zero  X
+          (strictly speaking, 0^0 is undefined).
+
+    NOTE: this function also throws exception  if  all  input  parameters  are
+          correct, but overflow was detected during calculations.
+
+    NOTE: this function performs a lot of checks;  if  you  need  really  high
+          performance, consider evaluating model  yourself,  without  checking
+          for degenerate cases.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 14.05.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static double logisticcalc4(double x, double a, double b, double c, double d)
+    {
+    
+        return lsfit.logisticcalc4(x, a, b, c, d, null);
+    }
+    
+    public static double logisticcalc4(double x, double a, double b, double c, double d, alglib.xparams _params)
+    {
+    
+        return lsfit.logisticcalc4(x, a, b, c, d, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates value of five-parameter logistic (5PL)  model  at
+    specified point X. 5PL model has following form:
+
+        F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G)
+
+    INPUT PARAMETERS:
+        X       -   current point, X>=0:
+                    * zero X is correctly handled even for B<=0
+                    * negative X results in exception.
+        A, B, C, D, G- parameters of 5PL model:
+                    * A is unconstrained
+                    * B is unconstrained; zero or negative values are handled
+                      correctly.
+                    * C>0, non-positive value results in exception
+                    * D is unconstrained
+                    * G>0, non-positive value results in exception
+
+    RESULT:
+        model value at X
+
+    NOTE: if B=0, denominator is assumed to be equal to Power(2.0,G) even  for
+          zero X (strictly speaking, 0^0 is undefined).
+
+    NOTE: this function also throws exception  if  all  input  parameters  are
+          correct, but overflow was detected during calculations.
+
+    NOTE: this function performs a lot of checks;  if  you  need  really  high
+          performance, consider evaluating model  yourself,  without  checking
+          for degenerate cases.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 14.05.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static double logisticcalc5(double x, double a, double b, double c, double d, double g)
+    {
+    
+        return lsfit.logisticcalc5(x, a, b, c, d, g, null);
+    }
+    
+    public static double logisticcalc5(double x, double a, double b, double c, double d, double g, alglib.xparams _params)
+    {
+    
+        return lsfit.logisticcalc5(x, a, b, c, d, g, _params);
+    }
+    
+    /*************************************************************************
+    This function fits four-parameter logistic (4PL) model  to  data  provided
+    by user. 4PL model has following form:
+
+        F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B))
+
+    Here:
+        * A, D - unconstrained (see LogisticFit4EC() for constrained 4PL)
+        * B>=0
+        * C>0
+
+    IMPORTANT: output of this function is constrained in  such  way that  B>0.
+               Because 4PL model is symmetric with respect to B, there  is  no
+               need to explore  B<0.  Constraining  B  makes  algorithm easier
+               to stabilize and debug.
+               Users  who  for  some  reason  prefer to work with negative B's
+               should transform output themselves (swap A and D, replace B  by
+               -B).
+
+    4PL fitting is implemented as follows:
+    * we perform small number of restarts from random locations which helps to
+      solve problem of bad local extrema. Locations are only partially  random
+      - we use input data to determine good  initial  guess,  but  we  include
+      controlled amount of randomness.
+    * we perform Levenberg-Marquardt fitting with very  tight  constraints  on
+      parameters B and C - it allows us to find good  initial  guess  for  the
+      second stage without risk of running into "flat spot".
+    * second  Levenberg-Marquardt  round  is   performed   without   excessive
+      constraints. Results from the previous round are used as initial guess.
+    * after fitting is done, we compare results with best values found so far,
+      rewrite "best solution" if needed, and move to next random location.
+
+    Overall algorithm is very stable and is not prone to  bad  local  extrema.
+    Furthermore, it automatically scales when input data have  very  large  or
+    very small range.
+
+    INPUT PARAMETERS:
+        X       -   array[N], stores X-values.
+                    MUST include only non-negative numbers  (but  may  include
+                    zero values). Can be unsorted.
+        Y       -   array[N], values to fit.
+        N       -   number of points. If N is less than  length  of  X/Y, only
+                    leading N elements are used.
+
+    OUTPUT PARAMETERS:
+        A, B, C, D- parameters of 4PL model
+        Rep     -   fitting report. This structure has many fields,  but  ONLY
+                    ONES LISTED BELOW ARE SET:
+                    * Rep.IterationsCount - number of iterations performed
+                    * Rep.RMSError - root-mean-square error
+                    * Rep.AvgError - average absolute error
+                    * Rep.AvgRelError - average relative error (calculated for
+                      non-zero Y-values)
+                    * Rep.MaxError - maximum absolute error
+                    * Rep.R2 - coefficient of determination,  R-squared.  This
+                      coefficient   is  calculated  as  R2=1-RSS/TSS  (in case
+                      of nonlinear  regression  there  are  multiple  ways  to
+                      define R2, each of them giving different results).
+
+    NOTE: for stability reasons the B parameter is restricted by [1/1000,1000]
+          range. It prevents  algorithm from making trial steps  deep into the
+          area of bad parameters.
+
+    NOTE: after  you  obtained  coefficients,  you  can  evaluate  model  with
+          LogisticCalc4() function.
+
+    NOTE: if you need better control over fitting process than provided by this
+          function, you may use LogisticFit45X().
+
+    NOTE: step is automatically scaled according to scale of parameters  being
+          fitted before we compare its length with EpsX. Thus,  this  function
+          can be used to fit data with very small or very large values without
+          changing EpsX.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 14.02.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void logisticfit4(double[] x, double[] y, int n, out double a, out double b, out double c, out double d, out lsfitreport rep)
+    {
+        a = 0;
+        b = 0;
+        c = 0;
+        d = 0;
+        rep = new lsfitreport();
+        lsfit.logisticfit4(x, y, n, ref a, ref b, ref c, ref d, rep.innerobj, null);
+    }
+    
+    public static void logisticfit4(double[] x, double[] y, int n, out double a, out double b, out double c, out double d, out lsfitreport rep, alglib.xparams _params)
+    {
+        a = 0;
+        b = 0;
+        c = 0;
+        d = 0;
+        rep = new lsfitreport();
+        lsfit.logisticfit4(x, y, n, ref a, ref b, ref c, ref d, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function fits four-parameter logistic (4PL) model  to  data  provided
+    by user, with optional constraints on parameters A and D.  4PL  model  has
+    following form:
+
+        F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B))
+
+    Here:
+        * A, D - with optional equality constraints
+        * B>=0
+        * C>0
+
+    IMPORTANT: output of this function is constrained in  such  way that  B>0.
+               Because 4PL model is symmetric with respect to B, there  is  no
+               need to explore  B<0.  Constraining  B  makes  algorithm easier
+               to stabilize and debug.
+               Users  who  for  some  reason  prefer to work with negative B's
+               should transform output themselves (swap A and D, replace B  by
+               -B).
+
+    4PL fitting is implemented as follows:
+    * we perform small number of restarts from random locations which helps to
+      solve problem of bad local extrema. Locations are only partially  random
+      - we use input data to determine good  initial  guess,  but  we  include
+      controlled amount of randomness.
+    * we perform Levenberg-Marquardt fitting with very  tight  constraints  on
+      parameters B and C - it allows us to find good  initial  guess  for  the
+      second stage without risk of running into "flat spot".
+    * second  Levenberg-Marquardt  round  is   performed   without   excessive
+      constraints. Results from the previous round are used as initial guess.
+    * after fitting is done, we compare results with best values found so far,
+      rewrite "best solution" if needed, and move to next random location.
+
+    Overall algorithm is very stable and is not prone to  bad  local  extrema.
+    Furthermore, it automatically scales when input data have  very  large  or
+    very small range.
+
+    INPUT PARAMETERS:
+        X       -   array[N], stores X-values.
+                    MUST include only non-negative numbers  (but  may  include
+                    zero values). Can be unsorted.
+        Y       -   array[N], values to fit.
+        N       -   number of points. If N is less than  length  of  X/Y, only
+                    leading N elements are used.
+        CnstrLeft-  optional equality constraint for model value at the   left
+                    boundary (at X=0). Specify NAN (Not-a-Number)  if  you  do
+                    not need constraint on the model value at X=0 (in C++  you
+                    can pass alglib::fp_nan as parameter, in  C#  it  will  be
+                    Double.NaN).
+                    See  below,  section  "EQUALITY  CONSTRAINTS"   for   more
+                    information about constraints.
+        CnstrRight- optional equality constraint for model value at X=infinity.
+                    Specify NAN (Not-a-Number) if you do not  need  constraint
+                    on the model value (in C++  you can pass alglib::fp_nan as
+                    parameter, in  C# it will  be Double.NaN).
+                    See  below,  section  "EQUALITY  CONSTRAINTS"   for   more
+                    information about constraints.
+
+    OUTPUT PARAMETERS:
+        A, B, C, D- parameters of 4PL model
+        Rep     -   fitting report. This structure has many fields,  but  ONLY
+                    ONES LISTED BELOW ARE SET:
+                    * Rep.IterationsCount - number of iterations performed
+                    * Rep.RMSError - root-mean-square error
+                    * Rep.AvgError - average absolute error
+                    * Rep.AvgRelError - average relative error (calculated for
+                      non-zero Y-values)
+                    * Rep.MaxError - maximum absolute error
+                    * Rep.R2 - coefficient of determination,  R-squared.  This
+                      coefficient   is  calculated  as  R2=1-RSS/TSS  (in case
+                      of nonlinear  regression  there  are  multiple  ways  to
+                      define R2, each of them giving different results).
+
+    NOTE: for stability reasons the B parameter is restricted by [1/1000,1000]
+          range. It prevents  algorithm from making trial steps  deep into the
+          area of bad parameters.
+
+    NOTE: after  you  obtained  coefficients,  you  can  evaluate  model  with
+          LogisticCalc4() function.
+
+    NOTE: if you need better control over fitting process than provided by this
+          function, you may use LogisticFit45X().
+
+    NOTE: step is automatically scaled according to scale of parameters  being
+          fitted before we compare its length with EpsX. Thus,  this  function
+          can be used to fit data with very small or very large values without
+          changing EpsX.
+
+    EQUALITY CONSTRAINTS ON PARAMETERS
+
+    4PL/5PL solver supports equality constraints on model values at  the  left
+    boundary (X=0) and right  boundary  (X=infinity).  These  constraints  are
+    completely optional and you can specify both of them, only  one  -  or  no
+    constraints at all.
+
+    Parameter  CnstrLeft  contains  left  constraint (or NAN for unconstrained
+    fitting), and CnstrRight contains right  one.  For  4PL,  left  constraint
+    ALWAYS corresponds to parameter A, and right one is ALWAYS  constraint  on
+    D. That's because 4PL model is normalized in such way that B>=0.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 14.02.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void logisticfit4ec(double[] x, double[] y, int n, double cnstrleft, double cnstrright, out double a, out double b, out double c, out double d, out lsfitreport rep)
+    {
+        a = 0;
+        b = 0;
+        c = 0;
+        d = 0;
+        rep = new lsfitreport();
+        lsfit.logisticfit4ec(x, y, n, cnstrleft, cnstrright, ref a, ref b, ref c, ref d, rep.innerobj, null);
+    }
+    
+    public static void logisticfit4ec(double[] x, double[] y, int n, double cnstrleft, double cnstrright, out double a, out double b, out double c, out double d, out lsfitreport rep, alglib.xparams _params)
+    {
+        a = 0;
+        b = 0;
+        c = 0;
+        d = 0;
+        rep = new lsfitreport();
+        lsfit.logisticfit4ec(x, y, n, cnstrleft, cnstrright, ref a, ref b, ref c, ref d, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function fits five-parameter logistic (5PL) model  to  data  provided
+    by user. 5PL model has following form:
+
+        F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G)
+
+    Here:
+        * A, D - unconstrained
+        * B - unconstrained
+        * C>0
+        * G>0
+
+    IMPORTANT: unlike in  4PL  fitting,  output  of  this  function   is   NOT
+               constrained in  such  way that B is guaranteed to be  positive.
+               Furthermore,  unlike  4PL,  5PL  model  is  NOT  symmetric with
+               respect to B, so you can NOT transform model to equivalent one,
+               with B having desired sign (>0 or <0).
+
+    5PL fitting is implemented as follows:
+    * we perform small number of restarts from random locations which helps to
+      solve problem of bad local extrema. Locations are only partially  random
+      - we use input data to determine good  initial  guess,  but  we  include
+      controlled amount of randomness.
+    * we perform Levenberg-Marquardt fitting with very  tight  constraints  on
+      parameters B and C - it allows us to find good  initial  guess  for  the
+      second stage without risk of running into "flat spot".  Parameter  G  is
+      fixed at G=1.
+    * second  Levenberg-Marquardt  round  is   performed   without   excessive
+      constraints on B and C, but with G still equal to 1.  Results  from  the
+      previous round are used as initial guess.
+    * third Levenberg-Marquardt round relaxes constraints on G  and  tries  two
+      different models - one with B>0 and one with B<0.
+    * after fitting is done, we compare results with best values found so far,
+      rewrite "best solution" if needed, and move to next random location.
+
+    Overall algorithm is very stable and is not prone to  bad  local  extrema.
+    Furthermore, it automatically scales when input data have  very  large  or
+    very small range.
+
+    INPUT PARAMETERS:
+        X       -   array[N], stores X-values.
+                    MUST include only non-negative numbers  (but  may  include
+                    zero values). Can be unsorted.
+        Y       -   array[N], values to fit.
+        N       -   number of points. If N is less than  length  of  X/Y, only
+                    leading N elements are used.
+
+    OUTPUT PARAMETERS:
+        A,B,C,D,G-  parameters of 5PL model
+        Rep     -   fitting report. This structure has many fields,  but  ONLY
+                    ONES LISTED BELOW ARE SET:
+                    * Rep.IterationsCount - number of iterations performed
+                    * Rep.RMSError - root-mean-square error
+                    * Rep.AvgError - average absolute error
+                    * Rep.AvgRelError - average relative error (calculated for
+                      non-zero Y-values)
+                    * Rep.MaxError - maximum absolute error
+                    * Rep.R2 - coefficient of determination,  R-squared.  This
+                      coefficient   is  calculated  as  R2=1-RSS/TSS  (in case
+                      of nonlinear  regression  there  are  multiple  ways  to
+                      define R2, each of them giving different results).
+
+    NOTE: for better stability B  parameter is restricted by [+-1/1000,+-1000]
+          range, and G is restricted by [1/10,10] range. It prevents algorithm
+          from making trial steps deep into the area of bad parameters.
+
+    NOTE: after  you  obtained  coefficients,  you  can  evaluate  model  with
+          LogisticCalc5() function.
+
+    NOTE: if you need better control over fitting process than provided by this
+          function, you may use LogisticFit45X().
+
+    NOTE: step is automatically scaled according to scale of parameters  being
+          fitted before we compare its length with EpsX. Thus,  this  function
+          can be used to fit data with very small or very large values without
+          changing EpsX.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 14.02.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void logisticfit5(double[] x, double[] y, int n, out double a, out double b, out double c, out double d, out double g, out lsfitreport rep)
+    {
+        a = 0;
+        b = 0;
+        c = 0;
+        d = 0;
+        g = 0;
+        rep = new lsfitreport();
+        lsfit.logisticfit5(x, y, n, ref a, ref b, ref c, ref d, ref g, rep.innerobj, null);
+    }
+    
+    public static void logisticfit5(double[] x, double[] y, int n, out double a, out double b, out double c, out double d, out double g, out lsfitreport rep, alglib.xparams _params)
+    {
+        a = 0;
+        b = 0;
+        c = 0;
+        d = 0;
+        g = 0;
+        rep = new lsfitreport();
+        lsfit.logisticfit5(x, y, n, ref a, ref b, ref c, ref d, ref g, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function fits five-parameter logistic (5PL) model  to  data  provided
+    by user, subject to optional equality constraints on parameters A  and  D.
+    5PL model has following form:
+
+        F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G)
+
+    Here:
+        * A, D - with optional equality constraints
+        * B - unconstrained
+        * C>0
+        * G>0
+
+    IMPORTANT: unlike in  4PL  fitting,  output  of  this  function   is   NOT
+               constrained in  such  way that B is guaranteed to be  positive.
+               Furthermore,  unlike  4PL,  5PL  model  is  NOT  symmetric with
+               respect to B, so you can NOT transform model to equivalent one,
+               with B having desired sign (>0 or <0).
+
+    5PL fitting is implemented as follows:
+    * we perform small number of restarts from random locations which helps to
+      solve problem of bad local extrema. Locations are only partially  random
+      - we use input data to determine good  initial  guess,  but  we  include
+      controlled amount of randomness.
+    * we perform Levenberg-Marquardt fitting with very  tight  constraints  on
+      parameters B and C - it allows us to find good  initial  guess  for  the
+      second stage without risk of running into "flat spot".  Parameter  G  is
+      fixed at G=1.
+    * second  Levenberg-Marquardt  round  is   performed   without   excessive
+      constraints on B and C, but with G still equal to 1.  Results  from  the
+      previous round are used as initial guess.
+    * third Levenberg-Marquardt round relaxes constraints on G  and  tries  two
+      different models - one with B>0 and one with B<0.
+    * after fitting is done, we compare results with best values found so far,
+      rewrite "best solution" if needed, and move to next random location.
+
+    Overall algorithm is very stable and is not prone to  bad  local  extrema.
+    Furthermore, it automatically scales when input data have  very  large  or
+    very small range.
+
+    INPUT PARAMETERS:
+        X       -   array[N], stores X-values.
+                    MUST include only non-negative numbers  (but  may  include
+                    zero values). Can be unsorted.
+        Y       -   array[N], values to fit.
+        N       -   number of points. If N is less than  length  of  X/Y, only
+                    leading N elements are used.
+        CnstrLeft-  optional equality constraint for model value at the   left
+                    boundary (at X=0). Specify NAN (Not-a-Number)  if  you  do
+                    not need constraint on the model value at X=0 (in C++  you
+                    can pass alglib::fp_nan as parameter, in  C#  it  will  be
+                    Double.NaN).
+                    See  below,  section  "EQUALITY  CONSTRAINTS"   for   more
+                    information about constraints.
+        CnstrRight- optional equality constraint for model value at X=infinity.
+                    Specify NAN (Not-a-Number) if you do not  need  constraint
+                    on the model value (in C++  you can pass alglib::fp_nan as
+                    parameter, in  C# it will  be Double.NaN).
+                    See  below,  section  "EQUALITY  CONSTRAINTS"   for   more
+                    information about constraints.
+
+    OUTPUT PARAMETERS:
+        A,B,C,D,G-  parameters of 5PL model
+        Rep     -   fitting report. This structure has many fields,  but  ONLY
+                    ONES LISTED BELOW ARE SET:
+                    * Rep.IterationsCount - number of iterations performed
+                    * Rep.RMSError - root-mean-square error
+                    * Rep.AvgError - average absolute error
+                    * Rep.AvgRelError - average relative error (calculated for
+                      non-zero Y-values)
+                    * Rep.MaxError - maximum absolute error
+                    * Rep.R2 - coefficient of determination,  R-squared.  This
+                      coefficient   is  calculated  as  R2=1-RSS/TSS  (in case
+                      of nonlinear  regression  there  are  multiple  ways  to
+                      define R2, each of them giving different results).
+
+    NOTE: for better stability B  parameter is restricted by [+-1/1000,+-1000]
+          range, and G is restricted by [1/10,10] range. It prevents algorithm
+          from making trial steps deep into the area of bad parameters.
+
+    NOTE: after  you  obtained  coefficients,  you  can  evaluate  model  with
+          LogisticCalc5() function.
+
+    NOTE: if you need better control over fitting process than provided by this
+          function, you may use LogisticFit45X().
+
+    NOTE: step is automatically scaled according to scale of parameters  being
+          fitted before we compare its length with EpsX. Thus,  this  function
+          can be used to fit data with very small or very large values without
+          changing EpsX.
+
+    EQUALITY CONSTRAINTS ON PARAMETERS
+
+    5PL solver supports equality constraints on model  values  at   the   left
+    boundary (X=0) and right  boundary  (X=infinity).  These  constraints  are
+    completely optional and you can specify both of them, only  one  -  or  no
+    constraints at all.
+
+    Parameter  CnstrLeft  contains  left  constraint (or NAN for unconstrained
+    fitting), and CnstrRight contains right  one.
+
+    Unlike 4PL one, 5PL model is NOT symmetric with respect to  change in sign
+    of B. Thus, negative B's are possible, and left constraint  may  constrain
+    parameter A (for positive B's)  -  or  parameter  D  (for  negative  B's).
+    Similarly changes meaning of right constraint.
+
+    You do not have to decide what parameter to  constrain  -  algorithm  will
+    automatically determine correct parameters as fitting progresses. However,
+    question highlighted above is important when you interpret fitting results.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 14.02.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void logisticfit5ec(double[] x, double[] y, int n, double cnstrleft, double cnstrright, out double a, out double b, out double c, out double d, out double g, out lsfitreport rep)
+    {
+        a = 0;
+        b = 0;
+        c = 0;
+        d = 0;
+        g = 0;
+        rep = new lsfitreport();
+        lsfit.logisticfit5ec(x, y, n, cnstrleft, cnstrright, ref a, ref b, ref c, ref d, ref g, rep.innerobj, null);
+    }
+    
+    public static void logisticfit5ec(double[] x, double[] y, int n, double cnstrleft, double cnstrright, out double a, out double b, out double c, out double d, out double g, out lsfitreport rep, alglib.xparams _params)
+    {
+        a = 0;
+        b = 0;
+        c = 0;
+        d = 0;
+        g = 0;
+        rep = new lsfitreport();
+        lsfit.logisticfit5ec(x, y, n, cnstrleft, cnstrright, ref a, ref b, ref c, ref d, ref g, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This is "expert" 4PL/5PL fitting function, which can be used if  you  need
+    better control over fitting process than provided  by  LogisticFit4()  or
+    LogisticFit5().
+
+    This function fits model of the form
+
+        F(x|A,B,C,D)   = D+(A-D)/(1+Power(x/C,B))           (4PL model)
+
+    or
+
+        F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G)    (5PL model)
+
+    Here:
+        * A, D - unconstrained
+        * B>=0 for 4PL, unconstrained for 5PL
+        * C>0
+        * G>0 (if present)
+
+    INPUT PARAMETERS:
+        X       -   array[N], stores X-values.
+                    MUST include only non-negative numbers  (but  may  include
+                    zero values). Can be unsorted.
+        Y       -   array[N], values to fit.
+        N       -   number of points. If N is less than  length  of  X/Y, only
+                    leading N elements are used.
+        CnstrLeft-  optional equality constraint for model value at the   left
+                    boundary (at X=0). Specify NAN (Not-a-Number)  if  you  do
+                    not need constraint on the model value at X=0 (in C++  you
+                    can pass alglib::fp_nan as parameter, in  C#  it  will  be
+                    Double.NaN).
+                    See  below,  section  "EQUALITY  CONSTRAINTS"   for   more
+                    information about constraints.
+        CnstrRight- optional equality constraint for model value at X=infinity.
+                    Specify NAN (Not-a-Number) if you do not  need  constraint
+                    on the model value (in C++  you can pass alglib::fp_nan as
+                    parameter, in  C# it will  be Double.NaN).
+                    See  below,  section  "EQUALITY  CONSTRAINTS"   for   more
+                    information about constraints.
+        Is4PL   -   whether 4PL or 5PL models are fitted
+        LambdaV -   regularization coefficient, LambdaV>=0.
+                    Set it to zero unless you know what you are doing.
+        EpsX    -   stopping condition (step size), EpsX>=0.
+                    Zero value means that small step is automatically chosen.
+                    See notes below for more information.
+        RsCnt   -   number of repeated restarts from  random  points.  4PL/5PL
+                    models are prone to problem of bad local extrema. Utilizing
+                    multiple random restarts allows  us  to  improve algorithm
+                    convergence.
+                    RsCnt>=0.
+                    Zero value means that function automatically choose  small
+                    amount of restarts (recommended).
+
+    OUTPUT PARAMETERS:
+        A, B, C, D- parameters of 4PL model
+        G       -   parameter of 5PL model; for Is4PL=True, G=1 is returned.
+        Rep     -   fitting report. This structure has many fields,  but  ONLY
+                    ONES LISTED BELOW ARE SET:
+                    * Rep.IterationsCount - number of iterations performed
+                    * Rep.RMSError - root-mean-square error
+                    * Rep.AvgError - average absolute error
+                    * Rep.AvgRelError - average relative error (calculated for
+                      non-zero Y-values)
+                    * Rep.MaxError - maximum absolute error
+                    * Rep.R2 - coefficient of determination,  R-squared.  This
+                      coefficient   is  calculated  as  R2=1-RSS/TSS  (in case
+                      of nonlinear  regression  there  are  multiple  ways  to
+                      define R2, each of them giving different results).
+
+    NOTE: for better stability B  parameter is restricted by [+-1/1000,+-1000]
+          range, and G is restricted by [1/10,10] range. It prevents algorithm
+          from making trial steps deep into the area of bad parameters.
+
+    NOTE: after  you  obtained  coefficients,  you  can  evaluate  model  with
+          LogisticCalc5() function.
+
+    NOTE: step is automatically scaled according to scale of parameters  being
+          fitted before we compare its length with EpsX. Thus,  this  function
+          can be used to fit data with very small or very large values without
+          changing EpsX.
+
+    EQUALITY CONSTRAINTS ON PARAMETERS
+
+    4PL/5PL solver supports equality constraints on model values at  the  left
+    boundary (X=0) and right  boundary  (X=infinity).  These  constraints  are
+    completely optional and you can specify both of them, only  one  -  or  no
+    constraints at all.
+
+    Parameter  CnstrLeft  contains  left  constraint (or NAN for unconstrained
+    fitting), and CnstrRight contains right  one.  For  4PL,  left  constraint
+    ALWAYS corresponds to parameter A, and right one is ALWAYS  constraint  on
+    D. That's because 4PL model is normalized in such way that B>=0.
+
+    For 5PL model things are different. Unlike  4PL  one,  5PL  model  is  NOT
+    symmetric with respect to  change  in  sign  of  B. Thus, negative B's are
+    possible, and left constraint may constrain parameter A (for positive B's)
+    - or parameter D (for negative B's). Similarly changes  meaning  of  right
+    constraint.
+
+    You do not have to decide what parameter to  constrain  -  algorithm  will
+    automatically determine correct parameters as fitting progresses. However,
+    question highlighted above is important when you interpret fitting results.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 14.02.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void logisticfit45x(double[] x, double[] y, int n, double cnstrleft, double cnstrright, bool is4pl, double lambdav, double epsx, int rscnt, out double a, out double b, out double c, out double d, out double g, out lsfitreport rep)
+    {
+        a = 0;
+        b = 0;
+        c = 0;
+        d = 0;
+        g = 0;
+        rep = new lsfitreport();
+        lsfit.logisticfit45x(x, y, n, cnstrleft, cnstrright, is4pl, lambdav, epsx, rscnt, ref a, ref b, ref c, ref d, ref g, rep.innerobj, null);
+    }
+    
+    public static void logisticfit45x(double[] x, double[] y, int n, double cnstrleft, double cnstrright, bool is4pl, double lambdav, double epsx, int rscnt, out double a, out double b, out double c, out double d, out double g, out lsfitreport rep, alglib.xparams _params)
+    {
+        a = 0;
+        b = 0;
+        c = 0;
+        d = 0;
+        g = 0;
+        rep = new lsfitreport();
+        lsfit.logisticfit45x(x, y, n, cnstrleft, cnstrright, is4pl, lambdav, epsx, rscnt, ref a, ref b, ref c, ref d, ref g, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        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, _params);
+    }
+    
+    /*************************************************************************
+    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).
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void barycentricfitfloaterhormann(double[] x, double[] y, int n, int m, out int info, out barycentricinterpolant b, out barycentricfitreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        b = new barycentricinterpolant();
+        rep = new barycentricfitreport();
+        lsfit.barycentricfitfloaterhormann(x, y, n, m, ref info, b.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        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, _params);
+    }
+            
+    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, null);
+    
+        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, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        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, _params);
+    }
+            
+    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, null);
+    
+        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, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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)
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+      -- 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, null);
+    }
+    
+    public static void spline1dfitcubic(double[] x, double[] y, int n, int m, out int info, out spline1dinterpolant s, out spline1dfitreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        lsfit.spline1dfitcubic(x, y, n, m, ref info, s.innerobj, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spline1dfitcubic(double[] x, double[] y, int m, out int info, out spline1dinterpolant s, out spline1dfitreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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).
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+      -- 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, null);
+    }
+    
+    public static void spline1dfithermite(double[] x, double[] y, int n, int m, out int info, out spline1dinterpolant s, out spline1dfitreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        s = new spline1dinterpolant();
+        rep = new spline1dfitreport();
+        lsfit.spline1dfithermite(x, y, n, m, ref info, s.innerobj, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spline1dfithermite(double[] x, double[] y, int m, out int info, out spline1dinterpolant s, out spline1dfitreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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.
+
+    IMPORTANT: if you want to perform  polynomial  fitting,  it  may  be  more
+               convenient to use PolynomialFit() function. This function gives
+               best  results  on  polynomial  problems  and  solves  numerical
+               stability  issues  which  arise  when   you   fit   high-degree
+               polynomials to your data.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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
+                    * R2                non-adjusted coefficient of determination
+                                        (non-weighted)
+                    * 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
+
+    ERRORS IN PARAMETERS
+
+    This  solver  also  calculates different kinds of errors in parameters and
+    fills corresponding fields of report:
+    * Rep.CovPar        covariance matrix for parameters, array[K,K].
+    * Rep.ErrPar        errors in parameters, array[K],
+                        errpar = sqrt(diag(CovPar))
+    * Rep.ErrCurve      vector of fit errors - standard deviations of empirical
+                        best-fit curve from "ideal" best-fit curve built  with
+                        infinite number of samples, array[N].
+                        errcurve = sqrt(diag(F*CovPar*F')),
+                        where F is functions matrix.
+    * Rep.Noise         vector of per-point estimates of noise, array[N]
+
+    NOTE:       noise in the data is estimated as follows:
+                * for fitting without user-supplied  weights  all  points  are
+                  assumed to have same level of noise, which is estimated from
+                  the data
+                * for fitting with user-supplied weights we assume that  noise
+                  level in I-th point is inversely proportional to Ith weight.
+                  Coefficient of proportionality is estimated from the data.
+
+    NOTE:       we apply small amount of regularization when we invert squared
+                Jacobian and calculate covariance matrix. It  guarantees  that
+                algorithm won't divide by zero  during  inversion,  but  skews
+                error estimates a bit (fractional error is about 10^-9).
+
+                However, we believe that this difference is insignificant  for
+                all practical purposes except for the situation when you  want
+                to compare ALGLIB results with "reference"  implementation  up
+                to the last significant digit.
+
+    NOTE:       covariance matrix is estimated using  correction  for  degrees
+                of freedom (covariances are divided by N-M instead of dividing
+                by N).
+
+      -- 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, null);
+    }
+    
+    public static void lsfitlinearw(double[] y, double[] w, double[,] fmatrix, int n, int m, out int info, out double[] c, out lsfitreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        lsfit.lsfitlinearw(y, w, fmatrix, n, m, ref info, ref c, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void lsfitlinearw(double[] y, double[] w, double[,] fmatrix, out int info, out double[] c, out lsfitreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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.
+
+    IMPORTANT: if you want to perform  polynomial  fitting,  it  may  be  more
+               convenient to use PolynomialFit() function. This function gives
+               best  results  on  polynomial  problems  and  solves  numerical
+               stability  issues  which  arise  when   you   fit   high-degree
+               polynomials to your data.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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:
+                    * R2                non-adjusted coefficient of determination
+                                        (non-weighted)
+                    * 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.
+
+    ERRORS IN PARAMETERS
+
+    This  solver  also  calculates different kinds of errors in parameters and
+    fills corresponding fields of report:
+    * Rep.CovPar        covariance matrix for parameters, array[K,K].
+    * Rep.ErrPar        errors in parameters, array[K],
+                        errpar = sqrt(diag(CovPar))
+    * Rep.ErrCurve      vector of fit errors - standard deviations of empirical
+                        best-fit curve from "ideal" best-fit curve built  with
+                        infinite number of samples, array[N].
+                        errcurve = sqrt(diag(F*CovPar*F')),
+                        where F is functions matrix.
+    * Rep.Noise         vector of per-point estimates of noise, array[N]
+
+    IMPORTANT:  errors  in  parameters  are  calculated  without  taking  into
+                account boundary/linear constraints! Presence  of  constraints
+                changes distribution of errors, but there is no  easy  way  to
+                account for constraints when you calculate covariance matrix.
+
+    NOTE:       noise in the data is estimated as follows:
+                * for fitting without user-supplied  weights  all  points  are
+                  assumed to have same level of noise, which is estimated from
+                  the data
+                * for fitting with user-supplied weights we assume that  noise
+                  level in I-th point is inversely proportional to Ith weight.
+                  Coefficient of proportionality is estimated from the data.
+
+    NOTE:       we apply small amount of regularization when we invert squared
+                Jacobian and calculate covariance matrix. It  guarantees  that
+                algorithm won't divide by zero  during  inversion,  but  skews
+                error estimates a bit (fractional error is about 10^-9).
+
+                However, we believe that this difference is insignificant  for
+                all practical purposes except for the situation when you  want
+                to compare ALGLIB results with "reference"  implementation  up
+                to the last significant digit.
+
+    NOTE:       covariance matrix is estimated using  correction  for  degrees
+                of freedom (covariances are divided by N-M instead of dividing
+                by N).
+
+      -- 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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        lsfit.lsfitlinearwc(y, w, fmatrix, cmatrix, n, m, k, ref info, ref c, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void lsfitlinearwc(double[] y, double[] w, double[,] fmatrix, double[,] cmatrix, out int info, out double[] c, out lsfitreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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.
+
+    IMPORTANT: if you want to perform  polynomial  fitting,  it  may  be  more
+               convenient to use PolynomialFit() function. This function gives
+               best  results  on  polynomial  problems  and  solves  numerical
+               stability  issues  which  arise  when   you   fit   high-degree
+               polynomials to your data.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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
+                    * R2                non-adjusted coefficient of determination
+                                        (non-weighted)
+                    * 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
+
+    ERRORS IN PARAMETERS
+
+    This  solver  also  calculates different kinds of errors in parameters and
+    fills corresponding fields of report:
+    * Rep.CovPar        covariance matrix for parameters, array[K,K].
+    * Rep.ErrPar        errors in parameters, array[K],
+                        errpar = sqrt(diag(CovPar))
+    * Rep.ErrCurve      vector of fit errors - standard deviations of empirical
+                        best-fit curve from "ideal" best-fit curve built  with
+                        infinite number of samples, array[N].
+                        errcurve = sqrt(diag(F*CovPar*F')),
+                        where F is functions matrix.
+    * Rep.Noise         vector of per-point estimates of noise, array[N]
+
+    NOTE:       noise in the data is estimated as follows:
+                * for fitting without user-supplied  weights  all  points  are
+                  assumed to have same level of noise, which is estimated from
+                  the data
+                * for fitting with user-supplied weights we assume that  noise
+                  level in I-th point is inversely proportional to Ith weight.
+                  Coefficient of proportionality is estimated from the data.
+
+    NOTE:       we apply small amount of regularization when we invert squared
+                Jacobian and calculate covariance matrix. It  guarantees  that
+                algorithm won't divide by zero  during  inversion,  but  skews
+                error estimates a bit (fractional error is about 10^-9).
+
+                However, we believe that this difference is insignificant  for
+                all practical purposes except for the situation when you  want
+                to compare ALGLIB results with "reference"  implementation  up
+                to the last significant digit.
+
+    NOTE:       covariance matrix is estimated using  correction  for  degrees
+                of freedom (covariances are divided by N-M instead of dividing
+                by N).
+
+      -- 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, null);
+    }
+    
+    public static void lsfitlinear(double[] y, double[,] fmatrix, int n, int m, out int info, out double[] c, out lsfitreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        lsfit.lsfitlinear(y, fmatrix, n, m, ref info, ref c, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void lsfitlinear(double[] y, double[,] fmatrix, out int info, out double[] c, out lsfitreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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.
+
+    IMPORTANT: if you want to perform  polynomial  fitting,  it  may  be  more
+               convenient to use PolynomialFit() function. This function gives
+               best  results  on  polynomial  problems  and  solves  numerical
+               stability  issues  which  arise  when   you   fit   high-degree
+               polynomials to your data.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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:
+                    * R2                non-adjusted coefficient of determination
+                                        (non-weighted)
+                    * 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.
+
+    ERRORS IN PARAMETERS
+
+    This  solver  also  calculates different kinds of errors in parameters and
+    fills corresponding fields of report:
+    * Rep.CovPar        covariance matrix for parameters, array[K,K].
+    * Rep.ErrPar        errors in parameters, array[K],
+                        errpar = sqrt(diag(CovPar))
+    * Rep.ErrCurve      vector of fit errors - standard deviations of empirical
+                        best-fit curve from "ideal" best-fit curve built  with
+                        infinite number of samples, array[N].
+                        errcurve = sqrt(diag(F*CovPar*F')),
+                        where F is functions matrix.
+    * Rep.Noise         vector of per-point estimates of noise, array[N]
+
+    IMPORTANT:  errors  in  parameters  are  calculated  without  taking  into
+                account boundary/linear constraints! Presence  of  constraints
+                changes distribution of errors, but there is no  easy  way  to
+                account for constraints when you calculate covariance matrix.
+
+    NOTE:       noise in the data is estimated as follows:
+                * for fitting without user-supplied  weights  all  points  are
+                  assumed to have same level of noise, which is estimated from
+                  the data
+                * for fitting with user-supplied weights we assume that  noise
+                  level in I-th point is inversely proportional to Ith weight.
+                  Coefficient of proportionality is estimated from the data.
+
+    NOTE:       we apply small amount of regularization when we invert squared
+                Jacobian and calculate covariance matrix. It  guarantees  that
+                algorithm won't divide by zero  during  inversion,  but  skews
+                error estimates a bit (fractional error is about 10^-9).
+
+                However, we believe that this difference is insignificant  for
+                all practical purposes except for the situation when you  want
+                to compare ALGLIB results with "reference"  implementation  up
+                to the last significant digit.
+
+    NOTE:       covariance matrix is estimated using  correction  for  degrees
+                of freedom (covariances are divided by N-M instead of dividing
+                by N).
+
+      -- 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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        lsfit.lsfitlinearc(y, fmatrix, cmatrix, n, m, k, ref info, ref c, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void lsfitlinearc(double[] y, double[,] fmatrix, double[,] cmatrix, out int info, out double[] c, out lsfitreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void lsfitcreatewf(double[,] x, double[] y, double[] w, double[] c, int n, int m, int k, double diffstep, out lsfitstate state, alglib.xparams _params)
+    {
+        state = new lsfitstate();
+        lsfit.lsfitcreatewf(x, y, w, c, n, m, k, diffstep, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void lsfitcreatewf(double[,] x, double[] y, double[] w, double[] c, double diffstep, out lsfitstate state, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void lsfitcreatef(double[,] x, double[] y, double[] c, int n, int m, int k, double diffstep, out lsfitstate state, alglib.xparams _params)
+    {
+        state = new lsfitstate();
+        lsfit.lsfitcreatef(x, y, c, n, m, k, diffstep, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void lsfitcreatef(double[,] x, double[] y, double[] c, double diffstep, out lsfitstate state, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void lsfitcreatewfg(double[,] x, double[] y, double[] w, double[] c, int n, int m, int k, bool cheapfg, out lsfitstate state, alglib.xparams _params)
+    {
+        state = new lsfitstate();
+        lsfit.lsfitcreatewfg(x, y, w, c, n, m, k, cheapfg, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void lsfitcreatewfg(double[,] x, double[] y, double[] w, double[] c, bool cheapfg, out lsfitstate state, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void lsfitcreatefg(double[,] x, double[] y, double[] c, int n, int m, int k, bool cheapfg, out lsfitstate state, alglib.xparams _params)
+    {
+        state = new lsfitstate();
+        lsfit.lsfitcreatefg(x, y, c, n, m, k, cheapfg, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void lsfitcreatefg(double[,] x, double[] y, double[] c, bool cheapfg, out lsfitstate state, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void lsfitcreatewfgh(double[,] x, double[] y, double[] w, double[] c, int n, int m, int k, out lsfitstate state, alglib.xparams _params)
+    {
+        state = new lsfitstate();
+        lsfit.lsfitcreatewfgh(x, y, w, c, n, m, k, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void lsfitcreatewfgh(double[,] x, double[] y, double[] w, double[] c, out lsfitstate state, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void lsfitcreatefgh(double[,] x, double[] y, double[] c, int n, int m, int k, out lsfitstate state, alglib.xparams _params)
+    {
+        state = new lsfitstate();
+        lsfit.lsfitcreatefgh(x, y, c, n, m, k, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void lsfitcreatefgh(double[,] x, double[] y, double[] c, out lsfitstate state, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Stopping conditions for nonlinear least squares fitting.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        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 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 epsx, int maxits)
+    {
+    
+        lsfit.lsfitsetcond(state.innerobj, epsx, maxits, null);
+    }
+    
+    public static void lsfitsetcond(lsfitstate state, double epsx, int maxits, alglib.xparams _params)
+    {
+    
+        lsfit.lsfitsetcond(state.innerobj, epsx, maxits, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lsfitsetstpmax(lsfitstate state, double stpmax, alglib.xparams _params)
+    {
+    
+        lsfit.lsfitsetstpmax(state.innerobj, stpmax, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lsfitsetxrep(lsfitstate state, bool needxrep, alglib.xparams _params)
+    {
+    
+        lsfit.lsfitsetxrep(state.innerobj, needxrep, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lsfitsetscale(lsfitstate state, double[] s, alglib.xparams _params)
+    {
+    
+        lsfit.lsfitsetscale(state.innerobj, s, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lsfitsetbc(lsfitstate state, double[] bndl, double[] bndu, alglib.xparams _params)
+    {
+    
+        lsfit.lsfitsetbc(state.innerobj, bndl, bndu, _params);
+    }
+    
+    /*************************************************************************
+    This function sets linear constraints for underlying optimizer
+
+    Linear constraints are inactive by default (after initial creation).
+    They are preserved until explicitly turned off with another SetLC() call.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        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
+
+    IMPORTANT: if you have linear constraints, it is strongly  recommended  to
+               set scale of variables with lsfitsetscale(). QP solver which is
+               used to calculate linearly constrained steps heavily relies  on
+               good scaling of input problems.
+
+    NOTE: linear  (non-box)  constraints  are  satisfied only approximately  -
+          there  always  exists some violation due  to  numerical  errors  and
+          algorithmic limitations.
+
+    NOTE: general linear constraints  add  significant  overhead  to  solution
+          process. Although solver performs roughly same amount of  iterations
+          (when compared  with  similar  box-only  constrained  problem), each
+          iteration   now    involves  solution  of  linearly  constrained  QP
+          subproblem, which requires ~3-5 times more Cholesky  decompositions.
+          Thus, if you can reformulate your problem in such way  this  it  has
+          only box constraints, it may be beneficial to do so.
+
+      -- ALGLIB --
+         Copyright 29.04.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitsetlc(lsfitstate state, double[,] c, int[] ct, int k)
+    {
+    
+        lsfit.lsfitsetlc(state.innerobj, c, ct, k, null);
+    }
+    
+    public static void lsfitsetlc(lsfitstate state, double[,] c, int[] ct, int k, alglib.xparams _params)
+    {
+    
+        lsfit.lsfitsetlc(state.innerobj, c, ct, k, _params);
+    }
+            
+    public static void lsfitsetlc(lsfitstate state, double[,] c, int[] ct)
+    {
+        int k;
+        if( (ap.rows(c)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'lsfitsetlc': looks like one of arguments has wrong size");
+    
+        k = ap.rows(c);
+        lsfit.lsfitsetlc(state.innerobj, c, ct, k, null);
+    
+        return;
+    }
+            
+    public static void lsfitsetlc(lsfitstate state, double[,] c, int[] ct, alglib.xparams _params)
+    {
+        int k;
+        if( (ap.rows(c)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'lsfitsetlc': looks like one of arguments has wrong size");
+    
+        k = ap.rows(c);
+        lsfit.lsfitsetlc(state.innerobj, c, ct, k, _params);
+    
+        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)
+    {
+    
+        return lsfit.lsfititeration(state.innerobj, null);
+    }
+    
+    public static bool lsfititeration(lsfitstate state, alglib.xparams _params)
+    {
+    
+        return lsfit.lsfititeration(state.innerobj, _params);
+    }
+    /*************************************************************************
+    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)
+    {
+        lsfitfit(state, func, rep, obj, null);
+    }
+    
+    public static void lsfitfit(lsfitstate state, ndimensional_pfunc func, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'lsfitfit()' (func is null)");
+        while( alglib.lsfititeration(state, _params) )
+        {
+            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)
+    {
+        lsfitfit(state, func, grad, rep, obj, null);
+    }
+    
+    public static void lsfitfit(lsfitstate state, ndimensional_pfunc func, ndimensional_pgrad grad, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        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, _params) )
+        {
+            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)
+    {
+        lsfitfit(state, func, grad, hess, rep, obj, null);
+    }
+    
+    public static void lsfitfit(lsfitstate state, ndimensional_pfunc func, ndimensional_pgrad grad, ndimensional_phess hess, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        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, _params) )
+        {
+            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    -   completion code:
+                        * -8    optimizer   detected  NAN/INF  in  the  target
+                                function and/or gradient
+                        * -7    gradient verification failed.
+                                See LSFitSetGradientCheck() for more information.
+                        * -3    inconsistent constraints
+                        *  2    relative step is no more than EpsX.
+                        *  5    MaxIts steps was taken
+                        *  7    stopping conditions are too stringent,
+                                further improvement is impossible
+        C       -   array[0..K-1], solution
+        Rep     -   optimization report. On success following fields are set:
+                    * R2                non-adjusted coefficient of determination
+                                        (non-weighted)
+                    * 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).
+
+    ERRORS IN PARAMETERS
+
+    This  solver  also  calculates different kinds of errors in parameters and
+    fills corresponding fields of report:
+    * Rep.CovPar        covariance matrix for parameters, array[K,K].
+    * Rep.ErrPar        errors in parameters, array[K],
+                        errpar = sqrt(diag(CovPar))
+    * Rep.ErrCurve      vector of fit errors - standard deviations of empirical
+                        best-fit curve from "ideal" best-fit curve built  with
+                        infinite number of samples, array[N].
+                        errcurve = sqrt(diag(J*CovPar*J')),
+                        where J is Jacobian matrix.
+    * Rep.Noise         vector of per-point estimates of noise, array[N]
+
+    IMPORTANT:  errors  in  parameters  are  calculated  without  taking  into
+                account boundary/linear constraints! Presence  of  constraints
+                changes distribution of errors, but there is no  easy  way  to
+                account for constraints when you calculate covariance matrix.
+
+    NOTE:       noise in the data is estimated as follows:
+                * for fitting without user-supplied  weights  all  points  are
+                  assumed to have same level of noise, which is estimated from
+                  the data
+                * for fitting with user-supplied weights we assume that  noise
+                  level in I-th point is inversely proportional to Ith weight.
+                  Coefficient of proportionality is estimated from the data.
+
+    NOTE:       we apply small amount of regularization when we invert squared
+                Jacobian and calculate covariance matrix. It  guarantees  that
+                algorithm won't divide by zero  during  inversion,  but  skews
+                error estimates a bit (fractional error is about 10^-9).
+
+                However, we believe that this difference is insignificant  for
+                all practical purposes except for the situation when you  want
+                to compare ALGLIB results with "reference"  implementation  up
+                to the last significant digit.
+
+    NOTE:       covariance matrix is estimated using  correction  for  degrees
+                of freedom (covariances are divided by N-M instead of dividing
+                by N).
+
+      -- 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, null);
+    }
+    
+    public static void lsfitresults(lsfitstate state, out int info, out double[] c, out lsfitreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        c = new double[0];
+        rep = new lsfitreport();
+        lsfit.lsfitresults(state.innerobj, ref info, ref c, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  subroutine  turns  on  verification  of  the  user-supplied analytic
+    gradient:
+    * user calls this subroutine before fitting begins
+    * LSFitFit() is called
+    * prior to actual fitting, for  each  point  in  data  set  X_i  and  each
+      component  of  parameters  being  fited C_j algorithm performs following
+      steps:
+      * two trial steps are made to C_j-TestStep*S[j] and C_j+TestStep*S[j],
+        where C_j is j-th parameter and S[j] is a scale of j-th parameter
+      * if needed, steps are bounded with respect to constraints on C[]
+      * F(X_i|C) is evaluated at these trial points
+      * we perform one more evaluation in the middle point of the interval
+      * we  build  cubic  model using function values and derivatives at trial
+        points and we compare its prediction with actual value in  the  middle
+        point
+      * in case difference between prediction and actual value is higher  than
+        some predetermined threshold, algorithm stops with completion code -7;
+        Rep.VarIdx is set to index of the parameter with incorrect derivative.
+    * after verification is over, algorithm proceeds to the actual optimization.
+
+    NOTE 1: verification needs N*K (points count * parameters count)  gradient
+            evaluations. It is very costly and you should use it only for  low
+            dimensional  problems,  when  you  want  to  be  sure  that you've
+            correctly calculated analytic derivatives. You should not  use  it
+            in the production code  (unless  you  want  to  check  derivatives
+            provided by some third party).
+
+    NOTE 2: you  should  carefully  choose  TestStep. Value which is too large
+            (so large that function behaviour is significantly non-cubic) will
+            lead to false alarms. You may use  different  step  for  different
+            parameters by means of setting scale with LSFitSetScale().
+
+    NOTE 3: this function may lead to false positives. In case it reports that
+            I-th  derivative was calculated incorrectly, you may decrease test
+            step  and  try  one  more  time  - maybe your function changes too
+            sharply  and  your  step  is  too  large for such rapidly chanding
+            function.
+
+    NOTE 4: this function works only for optimizers created with LSFitCreateWFG()
+            or LSFitCreateFG() constructors.
+
+    INPUT PARAMETERS:
+        State       -   structure used to store algorithm state
+        TestStep    -   verification step:
+                        * TestStep=0 turns verification off
+                        * TestStep>0 activates verification
+
+      -- ALGLIB --
+         Copyright 15.06.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lsfitsetgradientcheck(lsfitstate state, double teststep)
+    {
+    
+        lsfit.lsfitsetgradientcheck(state.innerobj, teststep, null);
+    }
+    
+    public static void lsfitsetgradientcheck(lsfitstate state, double teststep, alglib.xparams _params)
+    {
+    
+        lsfit.lsfitsetgradientcheck(state.innerobj, teststep, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    2-dimensional spline inteprolant
+    *************************************************************************/
+    public class spline2dinterpolant : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public spline2dinterpolant()
+        {
+            _innerobj = new spline2d.spline2dinterpolant();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new spline2dinterpolant((spline2d.spline2dinterpolant)_innerobj.make_copy());
+        }
+    
+        //
+        // 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;
+        }
+    }
+
+
+    /*************************************************************************
+    Nonlinear least squares solver used to fit 2D splines to data
+    *************************************************************************/
+    public class spline2dbuilder : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public spline2dbuilder()
+        {
+            _innerobj = new spline2d.spline2dbuilder();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new spline2dbuilder((spline2d.spline2dbuilder)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private spline2d.spline2dbuilder _innerobj;
+        public spline2d.spline2dbuilder innerobj { get { return _innerobj; } }
+        public spline2dbuilder(spline2d.spline2dbuilder obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    Spline 2D fitting report:
+        rmserror        RMS error
+        avgerror        average error
+        maxerror        maximum error
+        r2              coefficient of determination,  R-squared, 1-RSS/TSS
+    *************************************************************************/
+    public class spline2dfitreport : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public double rmserror { get { return _innerobj.rmserror; } set { _innerobj.rmserror = value; } }
+        public double avgerror { get { return _innerobj.avgerror; } set { _innerobj.avgerror = value; } }
+        public double maxerror { get { return _innerobj.maxerror; } set { _innerobj.maxerror = value; } }
+        public double r2 { get { return _innerobj.r2; } set { _innerobj.r2 = value; } }
+    
+        public spline2dfitreport()
+        {
+            _innerobj = new spline2d.spline2dfitreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new spline2dfitreport((spline2d.spline2dfitreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private spline2d.spline2dfitreport _innerobj;
+        public spline2d.spline2dfitreport innerobj { get { return _innerobj; } }
+        public spline2dfitreport(spline2d.spline2dfitreport 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 spline2dserialize(spline2dinterpolant obj, out string s_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        spline2d.spline2dalloc(s, obj.innerobj, null);
+        s.sstart_str();
+        spline2d.spline2dserialize(s, obj.innerobj, null);
+        s.stop();
+        s_out = s.get_string();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from string.
+    *************************************************************************/
+    public static void spline2dunserialize(string s_in, out spline2dinterpolant obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new spline2dinterpolant();
+        s.ustart_str(s_in);
+        spline2d.spline2dunserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to stream.
+    
+    Data stream generated by this function is same as  string  representation
+    generated  by  string  version  of  serializer - alphanumeric characters,
+    dots, underscores, minus signs, which are grouped into words separated by
+    spaces and CR+LF.
+    
+    We recommend you to read comments on string version of serializer to find
+    out more about serialization of AlGLIB objects.
+    *************************************************************************/
+    public static void spline2dserialize(spline2dinterpolant obj, System.IO.Stream stream_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        spline2d.spline2dalloc(s, obj.innerobj, null);
+        s.sstart_stream(stream_out);
+        spline2d.spline2dserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from stream.
+    *************************************************************************/
+    public static void spline2dunserialize(System.IO.Stream stream_in, out spline2dinterpolant obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new spline2dinterpolant();
+        s.ustart_stream(stream_in);
+        spline2d.spline2dunserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+    
+    /*************************************************************************
+    This subroutine calculates the value of the bilinear or bicubic spline  at
+    the given point X.
+
+    Input parameters:
+        C   -   2D spline object.
+                Built by spline2dbuildbilinearv or spline2dbuildbicubicv.
+        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)
+    {
+    
+        return spline2d.spline2dcalc(c.innerobj, x, y, null);
+    }
+    
+    public static double spline2dcalc(spline2dinterpolant c, double x, double y, alglib.xparams _params)
+    {
+    
+        return spline2d.spline2dcalc(c.innerobj, x, y, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void spline2ddiff(spline2dinterpolant c, double x, double y, out double f, out double fx, out double fy, out double fxy, alglib.xparams _params)
+    {
+        f = 0;
+        fx = 0;
+        fy = 0;
+        fxy = 0;
+        spline2d.spline2ddiff(c.innerobj, x, y, ref f, ref fx, ref fy, ref fxy, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine calculates bilinear or bicubic vector-valued spline at the
+    given point (X,Y).
+
+    If you need just some specific component of vector-valued spline, you  can
+    use spline2dcalcvi() function.
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+        X, Y-   point
+        F   -   output buffer, possibly preallocated array. In case array size
+                is large enough to store result, it is not reallocated.  Array
+                which is too short will be reallocated
+
+    OUTPUT PARAMETERS:
+        F   -   array[D] (or larger) which stores function values
+
+      -- ALGLIB PROJECT --
+         Copyright 01.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dcalcvbuf(spline2dinterpolant c, double x, double y, ref double[] f)
+    {
+    
+        spline2d.spline2dcalcvbuf(c.innerobj, x, y, ref f, null);
+    }
+    
+    public static void spline2dcalcvbuf(spline2dinterpolant c, double x, double y, ref double[] f, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dcalcvbuf(c.innerobj, x, y, ref f, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine calculates specific component of vector-valued bilinear or
+    bicubic spline at the given point (X,Y).
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+        X, Y-   point
+        I   -   component index, in [0,D). An exception is generated for out
+                of range values.
+
+    RESULT:
+        value of I-th component
+
+      -- ALGLIB PROJECT --
+         Copyright 01.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static double spline2dcalcvi(spline2dinterpolant c, double x, double y, int i)
+    {
+    
+        return spline2d.spline2dcalcvi(c.innerobj, x, y, i, null);
+    }
+    
+    public static double spline2dcalcvi(spline2dinterpolant c, double x, double y, int i, alglib.xparams _params)
+    {
+    
+        return spline2d.spline2dcalcvi(c.innerobj, x, y, i, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine calculates bilinear or bicubic vector-valued spline at the
+    given point (X,Y).
+
+    INPUT PARAMETERS:
+        C   -   spline interpolant.
+        X, Y-   point
+
+    OUTPUT PARAMETERS:
+        F   -   array[D] which stores function values.  F is out-parameter and
+                it  is  reallocated  after  call to this function. In case you
+                want  to    reuse  previously  allocated  F,   you   may   use
+                Spline2DCalcVBuf(),  which  reallocates  F only when it is too
+                small.
+
+      -- ALGLIB PROJECT --
+         Copyright 16.04.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dcalcv(spline2dinterpolant c, double x, double y, out double[] f)
+    {
+        f = new double[0];
+        spline2d.spline2dcalcv(c.innerobj, x, y, ref f, null);
+    }
+    
+    public static void spline2dcalcv(spline2dinterpolant c, double x, double y, out double[] f, alglib.xparams _params)
+    {
+        f = new double[0];
+        spline2d.spline2dcalcv(c.innerobj, x, y, ref f, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine calculates value of  specific  component  of  bilinear  or
+    bicubic vector-valued spline and its derivatives.
+
+    Input parameters:
+        C   -   spline interpolant.
+        X, Y-   point
+        I   -   component index, in [0,D)
+
+    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 spline2ddiffvi(spline2dinterpolant c, double x, double y, int i, out double f, out double fx, out double fy, out double fxy)
+    {
+        f = 0;
+        fx = 0;
+        fy = 0;
+        fxy = 0;
+        spline2d.spline2ddiffvi(c.innerobj, x, y, i, ref f, ref fx, ref fy, ref fxy, null);
+    }
+    
+    public static void spline2ddiffvi(spline2dinterpolant c, double x, double y, int i, out double f, out double fx, out double fy, out double fxy, alglib.xparams _params)
+    {
+        f = 0;
+        fx = 0;
+        fy = 0;
+        fxy = 0;
+        spline2d.spline2ddiffvi(c.innerobj, x, y, i, ref f, ref fx, ref fy, ref fxy, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void spline2dlintransxy(spline2dinterpolant c, double ax, double bx, double ay, double by, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dlintransxy(c.innerobj, ax, bx, ay, by, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void spline2dlintransf(spline2dinterpolant c, double a, double b, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dlintransf(c.innerobj, a, b, _params);
+    }
+    
+    /*************************************************************************
+    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, out spline2dinterpolant cc)
+    {
+        cc = new spline2dinterpolant();
+        spline2d.spline2dcopy(c.innerobj, cc.innerobj, null);
+    }
+    
+    public static void spline2dcopy(spline2dinterpolant c, out spline2dinterpolant cc, alglib.xparams _params)
+    {
+        cc = new spline2dinterpolant();
+        spline2d.spline2dcopy(c.innerobj, cc.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void spline2dresamplebicubic(double[,] a, int oldheight, int oldwidth, out double[,] b, int newheight, int newwidth, alglib.xparams _params)
+    {
+        b = new double[0,0];
+        spline2d.spline2dresamplebicubic(a, oldheight, oldwidth, ref b, newheight, newwidth, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void spline2dresamplebilinear(double[,] a, int oldheight, int oldwidth, out double[,] b, int newheight, int newwidth, alglib.xparams _params)
+    {
+        b = new double[0,0];
+        spline2d.spline2dresamplebilinear(a, oldheight, oldwidth, ref b, newheight, newwidth, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine builds bilinear vector-valued spline.
+
+    Input parameters:
+        X   -   spline abscissas, array[0..N-1]
+        Y   -   spline ordinates, array[0..M-1]
+        F   -   function values, array[0..M*N*D-1]:
+                * first D elements store D values at (X[0],Y[0])
+                * next D elements store D values at (X[1],Y[0])
+                * general form - D function values at (X[i],Y[j]) are stored
+                  at F[D*(J*N+I)...D*(J*N+I)+D-1].
+        M,N -   grid size, M>=2, N>=2
+        D   -   vector dimension, D>=1
+
+    Output parameters:
+        C   -   spline interpolant
+
+      -- ALGLIB PROJECT --
+         Copyright 16.04.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildbilinearv(double[] x, int n, double[] y, int m, double[] f, int d, out spline2dinterpolant c)
+    {
+        c = new spline2dinterpolant();
+        spline2d.spline2dbuildbilinearv(x, n, y, m, f, d, c.innerobj, null);
+    }
+    
+    public static void spline2dbuildbilinearv(double[] x, int n, double[] y, int m, double[] f, int d, out spline2dinterpolant c, alglib.xparams _params)
+    {
+        c = new spline2dinterpolant();
+        spline2d.spline2dbuildbilinearv(x, n, y, m, f, d, c.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine builds bicubic vector-valued spline.
+
+    Input parameters:
+        X   -   spline abscissas, array[0..N-1]
+        Y   -   spline ordinates, array[0..M-1]
+        F   -   function values, array[0..M*N*D-1]:
+                * first D elements store D values at (X[0],Y[0])
+                * next D elements store D values at (X[1],Y[0])
+                * general form - D function values at (X[i],Y[j]) are stored
+                  at F[D*(J*N+I)...D*(J*N+I)+D-1].
+        M,N -   grid size, M>=2, N>=2
+        D   -   vector dimension, D>=1
+
+    Output parameters:
+        C   -   spline interpolant
+
+      -- ALGLIB PROJECT --
+         Copyright 16.04.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildbicubicv(double[] x, int n, double[] y, int m, double[] f, int d, out spline2dinterpolant c)
+    {
+        c = new spline2dinterpolant();
+        spline2d.spline2dbuildbicubicv(x, n, y, m, f, d, c.innerobj, null);
+    }
+    
+    public static void spline2dbuildbicubicv(double[] x, int n, double[] y, int m, double[] f, int d, out spline2dinterpolant c, alglib.xparams _params)
+    {
+        c = new spline2dinterpolant();
+        spline2d.spline2dbuildbicubicv(x, n, y, m, f, d, c.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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)
+        D   -   number of components
+        Tbl -   coefficients table, unpacked format,
+                D - components: [0..(N-1)*(M-1)*D-1, 0..19].
+                For T=0..D-1 (component index), I = 0...N-2 (x index),
+                J=0..M-2 (y index):
+                    K :=  T + I*D + J*D*(N-1)
+
+                    K-th row stores decomposition for T-th component of the
+                    vector-valued function
+
+                    Tbl[K,0] = X[i]
+                    Tbl[K,1] = X[i+1]
+                    Tbl[K,2] = Y[j]
+                    Tbl[K,3] = Y[j+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]*(t^i)*(u^j), i=0..3, j=0..3)
+                    t = x-x[j]
+                    u = y-y[i]
+
+      -- ALGLIB PROJECT --
+         Copyright 16.04.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dunpackv(spline2dinterpolant c, out int m, out int n, out int d, out double[,] tbl)
+    {
+        m = 0;
+        n = 0;
+        d = 0;
+        tbl = new double[0,0];
+        spline2d.spline2dunpackv(c.innerobj, ref m, ref n, ref d, ref tbl, null);
+    }
+    
+    public static void spline2dunpackv(spline2dinterpolant c, out int m, out int n, out int d, out double[,] tbl, alglib.xparams _params)
+    {
+        m = 0;
+        n = 0;
+        d = 0;
+        tbl = new double[0,0];
+        spline2d.spline2dunpackv(c.innerobj, ref m, ref n, ref d, ref tbl, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine was deprecated in ALGLIB 3.6.0
+
+    We recommend you to switch  to  Spline2DBuildBilinearV(),  which  is  more
+    flexible and accepts its arguments in more convenient order.
+
+      -- 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, null);
+    }
+    
+    public static void spline2dbuildbilinear(double[] x, double[] y, double[,] f, int m, int n, out spline2dinterpolant c, alglib.xparams _params)
+    {
+        c = new spline2dinterpolant();
+        spline2d.spline2dbuildbilinear(x, y, f, m, n, c.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine was deprecated in ALGLIB 3.6.0
+
+    We recommend you to switch  to  Spline2DBuildBicubicV(),  which  is  more
+    flexible and accepts its arguments in more convenient order.
+
+      -- 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, null);
+    }
+    
+    public static void spline2dbuildbicubic(double[] x, double[] y, double[,] f, int m, int n, out spline2dinterpolant c, alglib.xparams _params)
+    {
+        c = new spline2dinterpolant();
+        spline2d.spline2dbuildbicubic(x, y, f, m, n, c.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine was deprecated in ALGLIB 3.6.0
+
+    We recommend you to switch  to  Spline2DUnpackV(),  which is more flexible
+    and accepts its arguments in more convenient order.
+
+      -- 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, null);
+    }
+    
+    public static void spline2dunpack(spline2dinterpolant c, out int m, out int n, out double[,] tbl, alglib.xparams _params)
+    {
+        m = 0;
+        n = 0;
+        tbl = new double[0,0];
+        spline2d.spline2dunpack(c.innerobj, ref m, ref n, ref tbl, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine creates least squares solver used to  fit  2D  splines  to
+    irregularly sampled (scattered) data.
+
+    Solver object is used to perform spline fits as follows:
+    * solver object is created with spline2dbuildercreate() function
+    * dataset is added with spline2dbuildersetpoints() function
+    * fit area is chosen:
+      * spline2dbuildersetarea()     - for user-defined area
+      * spline2dbuildersetareaauto() - for automatically chosen area
+    * number of grid nodes is chosen with spline2dbuildersetgrid()
+    * prior term is chosen with one of the following functions:
+      * spline2dbuildersetlinterm()   to set linear prior
+      * spline2dbuildersetconstterm() to set constant prior
+      * spline2dbuildersetzeroterm()  to set zero prior
+      * spline2dbuildersetuserterm()  to set user-defined constant prior
+    * solver algorithm is chosen with either:
+      * spline2dbuildersetalgoblocklls() - BlockLLS algorithm, medium-scale problems
+      * spline2dbuildersetalgofastddm()  - FastDDM algorithm, large-scale problems
+    * finally, fitting itself is performed with spline2dfit() function.
+
+    Most of the steps above can be omitted,  solver  is  configured with  good
+    defaults. The minimum is to call:
+    * spline2dbuildercreate() to create solver object
+    * spline2dbuildersetpoints() to specify dataset
+    * spline2dbuildersetgrid() to tell how many nodes you need
+    * spline2dfit() to perform fit
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        D   -   positive number, number of Y-components: D=1 for simple scalar
+                fit, D>1 for vector-valued spline fitting.
+
+    OUTPUT PARAMETERS:
+        S   -   solver object
+
+      -- ALGLIB PROJECT --
+         Copyright 29.01.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildercreate(int d, out spline2dbuilder state)
+    {
+        state = new spline2dbuilder();
+        spline2d.spline2dbuildercreate(d, state.innerobj, null);
+    }
+    
+    public static void spline2dbuildercreate(int d, out spline2dbuilder state, alglib.xparams _params)
+    {
+        state = new spline2dbuilder();
+        spline2d.spline2dbuildercreate(d, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function sets constant prior term (model is a sum of  bicubic  spline
+    and global prior, which can be linear, constant, user-defined  constant or
+    zero).
+
+    Constant prior term is determined by least squares fitting.
+
+    INPUT PARAMETERS:
+        S       -   spline builder
+        V       -   value for user-defined prior
+
+      -- ALGLIB --
+         Copyright 01.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildersetuserterm(spline2dbuilder state, double v)
+    {
+    
+        spline2d.spline2dbuildersetuserterm(state.innerobj, v, null);
+    }
+    
+    public static void spline2dbuildersetuserterm(spline2dbuilder state, double v, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dbuildersetuserterm(state.innerobj, v, _params);
+    }
+    
+    /*************************************************************************
+    This function sets linear prior term (model is a sum of bicubic spline and
+    global  prior,  which  can  be  linear, constant, user-defined constant or
+    zero).
+
+    Linear prior term is determined by least squares fitting.
+
+    INPUT PARAMETERS:
+        S       -   spline builder
+
+      -- ALGLIB --
+         Copyright 01.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildersetlinterm(spline2dbuilder state)
+    {
+    
+        spline2d.spline2dbuildersetlinterm(state.innerobj, null);
+    }
+    
+    public static void spline2dbuildersetlinterm(spline2dbuilder state, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dbuildersetlinterm(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function sets constant prior term (model is a sum of  bicubic  spline
+    and global prior, which can be linear, constant, user-defined  constant or
+    zero).
+
+    Constant prior term is determined by least squares fitting.
+
+    INPUT PARAMETERS:
+        S       -   spline builder
+
+      -- ALGLIB --
+         Copyright 01.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildersetconstterm(spline2dbuilder state)
+    {
+    
+        spline2d.spline2dbuildersetconstterm(state.innerobj, null);
+    }
+    
+    public static void spline2dbuildersetconstterm(spline2dbuilder state, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dbuildersetconstterm(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function sets zero prior term (model is a sum of bicubic  spline  and
+    global  prior,  which  can  be  linear, constant, user-defined constant or
+    zero).
+
+    INPUT PARAMETERS:
+        S       -   spline builder
+
+      -- ALGLIB --
+         Copyright 01.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildersetzeroterm(spline2dbuilder state)
+    {
+    
+        spline2d.spline2dbuildersetzeroterm(state.innerobj, null);
+    }
+    
+    public static void spline2dbuildersetzeroterm(spline2dbuilder state, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dbuildersetzeroterm(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function adds dataset to the builder object.
+
+    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       -   spline 2D builder object
+        XY      -   points, array[N,2+D]. One  row  corresponds to  one  point
+                    in the dataset. First 2  elements  are  coordinates,  next
+                    D  elements are function values. Array may  be larger than
+                    specified, in  this  case  only leading [N,NX+NY] elements
+                    will be used.
+        N       -   number of points in the dataset
+
+      -- ALGLIB --
+         Copyright 05.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildersetpoints(spline2dbuilder state, double[,] xy, int n)
+    {
+    
+        spline2d.spline2dbuildersetpoints(state.innerobj, xy, n, null);
+    }
+    
+    public static void spline2dbuildersetpoints(spline2dbuilder state, double[,] xy, int n, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dbuildersetpoints(state.innerobj, xy, n, _params);
+    }
+    
+    /*************************************************************************
+    This function sets area where 2D spline interpolant is built. "Auto" means
+    that area extent is determined automatically from dataset extent.
+
+    INPUT PARAMETERS:
+        S       -   spline 2D builder object
+
+      -- ALGLIB --
+         Copyright 05.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildersetareaauto(spline2dbuilder state)
+    {
+    
+        spline2d.spline2dbuildersetareaauto(state.innerobj, null);
+    }
+    
+    public static void spline2dbuildersetareaauto(spline2dbuilder state, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dbuildersetareaauto(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function  sets  area  where  2D  spline  interpolant  is   built  to
+    user-defined one: [XA,XB]*[YA,YB]
+
+    INPUT PARAMETERS:
+        S       -   spline 2D builder object
+        XA,XB   -   spatial extent in the first (X) dimension, XA<XB
+        YA,YB   -   spatial extent in the second (Y) dimension, YA<YB
+
+      -- ALGLIB --
+         Copyright 05.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildersetarea(spline2dbuilder state, double xa, double xb, double ya, double yb)
+    {
+    
+        spline2d.spline2dbuildersetarea(state.innerobj, xa, xb, ya, yb, null);
+    }
+    
+    public static void spline2dbuildersetarea(spline2dbuilder state, double xa, double xb, double ya, double yb, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dbuildersetarea(state.innerobj, xa, xb, ya, yb, _params);
+    }
+    
+    /*************************************************************************
+    This  function  sets  nodes  count  for  2D spline interpolant. Fitting is
+    performed on area defined with one of the "setarea"  functions;  this  one
+    sets number of nodes placed upon the fitting area.
+
+    INPUT PARAMETERS:
+        S       -   spline 2D builder object
+        KX      -   nodes count for the first (X) dimension; fitting  interval
+                    [XA,XB] is separated into KX-1 subintervals, with KX nodes
+                    created at the boundaries.
+        KY      -   nodes count for the first (Y) dimension; fitting  interval
+                    [YA,YB] is separated into KY-1 subintervals, with KY nodes
+                    created at the boundaries.
+
+    NOTE: at  least  4  nodes  is  created in each dimension, so KX and KY are
+          silently increased if needed.
+
+      -- ALGLIB --
+         Copyright 05.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildersetgrid(spline2dbuilder state, int kx, int ky)
+    {
+    
+        spline2d.spline2dbuildersetgrid(state.innerobj, kx, ky, null);
+    }
+    
+    public static void spline2dbuildersetgrid(spline2dbuilder state, int kx, int ky, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dbuildersetgrid(state.innerobj, kx, ky, _params);
+    }
+    
+    /*************************************************************************
+    This  function  allows  you to choose least squares solver used to perform
+    fitting. This function sets solver algorithm to "FastDDM", which  performs
+    fast parallel fitting by splitting problem into smaller chunks and merging
+    results together.
+
+    This solver is optimized for large-scale problems, starting  from  256x256
+    grids, and up to 10000x10000 grids. Of course, it will  work  for  smaller
+    grids too.
+
+    More detailed description of the algorithm is given below:
+    * algorithm generates hierarchy  of  nested  grids,  ranging  from  ~16x16
+      (topmost "layer" of the model) to ~KX*KY one (final layer). Upper layers
+      model global behavior of the function, lower layers are  used  to  model
+      fine details. Moving from layer to layer doubles grid density.
+    * fitting  is  started  from  topmost  layer, subsequent layers are fitted
+      using residuals from previous ones.
+    * user may choose to skip generation of upper layers and generate  only  a
+      few bottom ones, which  will  result  in  much  better  performance  and
+      parallelization efficiency, at the cost of algorithm inability to "patch"
+      large holes in the dataset.
+    * every layer is regularized using progressively increasing regularization
+      coefficient; thus, increasing  LambdaV  penalizes  fine  details  first,
+      leaving lower frequencies almost intact for a while.
+    * after fitting is done, all layers are merged together into  one  bicubic
+      spline
+
+    IMPORTANT: regularization coefficient used by  this  solver  is  different
+               from the one used by  BlockLLS.  Latter  utilizes  nonlinearity
+               penalty,  which  is  global  in  nature  (large  regularization
+               results in global linear trend being  extracted);  this  solver
+               uses another, localized form of penalty, which is suitable  for
+               parallel processing.
+
+    Notes on memory and performance:
+    * memory requirements: most memory is consumed  during  modeling   of  the
+      higher layers; ~[512*NPoints] bytes is required for a  model  with  full
+      hierarchy of grids being generated. However, if you skip a  few  topmost
+      layers, you will get nearly constant (wrt. points count and  grid  size)
+      memory consumption.
+    * serial running time: O(K*K)+O(NPoints) for a KxK grid
+    * parallelism potential: good. You may get  nearly  linear  speed-up  when
+      performing fitting with just a few layers. Adding more layers results in
+      model becoming more global, which somewhat  reduces  efficiency  of  the
+      parallel code.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        S       -   spline 2D builder object
+        NLayers -   number of layers in the model:
+                    * NLayers>=1 means that up  to  chosen  number  of  bottom
+                      layers is fitted
+                    * NLayers=0 means that maximum number of layers is  chosen
+                      (according to current grid size)
+                    * NLayers<=-1 means that up to |NLayers| topmost layers is
+                      skipped
+                    Recommendations:
+                    * good "default" value is 2 layers
+                    * you may need  more  layers,  if  your  dataset  is  very
+                      irregular and you want to "patch"  large  holes.  For  a
+                      grid step H (equal to AreaWidth/GridSize) you may expect
+                      that last layer reproduces variations at distance H (and
+                      can patch holes that wide); that higher  layers  operate
+                      at distances 2*H, 4*H, 8*H and so on.
+                    * good value for "bullletproof" mode is  NLayers=0,  which
+                      results in complete hierarchy of layers being generated.
+        LambdaV -   regularization coefficient, chosen in such a way  that  it
+                    penalizes bottom layers (fine details) first.
+                    LambdaV>=0, zero value means that no penalty is applied.
+
+      -- ALGLIB --
+         Copyright 05.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildersetalgofastddm(spline2dbuilder state, int nlayers, double lambdav)
+    {
+    
+        spline2d.spline2dbuildersetalgofastddm(state.innerobj, nlayers, lambdav, null);
+    }
+    
+    public static void spline2dbuildersetalgofastddm(spline2dbuilder state, int nlayers, double lambdav, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dbuildersetalgofastddm(state.innerobj, nlayers, lambdav, _params);
+    }
+    
+    /*************************************************************************
+    This  function  allows  you to choose least squares solver used to perform
+    fitting. This function sets solver algorithm to "BlockLLS", which performs
+    least squares fitting  with  fast  sparse  direct  solver,  with  optional
+    nonsmoothness penalty being applied.
+
+    Nonlinearity penalty has the following form:
+
+                              [                                            ]
+        P() ~ Lambda* integral[ (d2S/dx2)^2 + 2*(d2S/dxdy)^2 + (d2S/dy2)^2 ]dxdy
+                              [                                            ]
+
+    here integral is calculated over entire grid, and "~" means "proportional"
+    because integral is normalized after calcilation. Extremely  large  values
+    of Lambda result in linear fit being performed.
+
+    NOTE: this algorithm is the most robust and controllable one,  but  it  is
+          limited by 512x512 grids and (say) up to 1.000.000 points.  However,
+          ALGLIB has one more  spline  solver:  FastDDM  algorithm,  which  is
+          intended for really large-scale problems (in 10M-100M range). FastDDM
+          algorithm also has better parallelism properties.
+
+    More information on BlockLLS solver:
+    * memory requirements: ~[32*K^3+256*NPoints]  bytes  for  KxK  grid   with
+      NPoints-sized dataset
+    * serial running time: O(K^4+NPoints)
+    * parallelism potential: limited. You may get some sublinear gain when
+      working with large grids (K's in 256..512 range)
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        S       -   spline 2D builder object
+        LambdaNS-   non-negative value:
+                    * positive value means that some smoothing is applied
+                    * zero value means  that  no  smoothing  is  applied,  and
+                      corresponding entries of design matrix  are  numerically
+                      zero and dropped from consideration.
+
+      -- ALGLIB --
+         Copyright 05.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildersetalgoblocklls(spline2dbuilder state, double lambdans)
+    {
+    
+        spline2d.spline2dbuildersetalgoblocklls(state.innerobj, lambdans, null);
+    }
+    
+    public static void spline2dbuildersetalgoblocklls(spline2dbuilder state, double lambdans, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dbuildersetalgoblocklls(state.innerobj, lambdans, _params);
+    }
+    
+    /*************************************************************************
+    This  function  allows  you to choose least squares solver used to perform
+    fitting. This function sets solver algorithm to "NaiveLLS".
+
+    IMPORTANT: NaiveLLS is NOT intended to be used in  real  life  code!  This
+               algorithm solves problem by generated dense (K^2)x(K^2+NPoints)
+               matrix and solves  linear  least  squares  problem  with  dense
+               solver.
+
+               It is here just  to  test  BlockLLS  against  reference  solver
+               (and maybe for someone trying to compare well optimized  solver
+               against straightforward approach to the LLS problem).
+
+    More information on naive LLS solver:
+    * memory requirements: ~[8*K^4+256*NPoints] bytes for KxK grid.
+    * serial running time: O(K^6+NPoints) for KxK grid
+    * when compared with BlockLLS,  NaiveLLS  has ~K  larger memory demand and
+      ~K^2  larger running time.
+
+    INPUT PARAMETERS:
+        S       -   spline 2D builder object
+        LambdaNS-   nonsmoothness penalty
+
+      -- ALGLIB --
+         Copyright 05.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dbuildersetalgonaivells(spline2dbuilder state, double lambdans)
+    {
+    
+        spline2d.spline2dbuildersetalgonaivells(state.innerobj, lambdans, null);
+    }
+    
+    public static void spline2dbuildersetalgonaivells(spline2dbuilder state, double lambdans, alglib.xparams _params)
+    {
+    
+        spline2d.spline2dbuildersetalgonaivells(state.innerobj, lambdans, _params);
+    }
+    
+    /*************************************************************************
+    This function fits bicubic spline to current dataset, using current  area/
+    grid and current LLS solver.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        State   -   spline 2D builder object
+
+    OUTPUT PARAMETERS:
+        S       -   2D spline, fit result
+        Rep     -   fitting report, which provides some additional info  about
+                    errors, R2 coefficient and so on.
+
+      -- ALGLIB --
+         Copyright 05.02.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spline2dfit(spline2dbuilder state, out spline2dinterpolant s, out spline2dfitreport rep)
+    {
+        s = new spline2dinterpolant();
+        rep = new spline2dfitreport();
+        spline2d.spline2dfit(state.innerobj, s.innerobj, rep.innerobj, null);
+    }
+    
+    public static void spline2dfit(spline2dbuilder state, out spline2dinterpolant s, out spline2dfitreport rep, alglib.xparams _params)
+    {
+        s = new spline2dinterpolant();
+        rep = new spline2dfitreport();
+        spline2d.spline2dfit(state.innerobj, s.innerobj, rep.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Buffer object which is used to perform nearest neighbor  requests  in  the
+    multithreaded mode (multiple threads working with same KD-tree object).
+
+    This object should be created with KDTreeCreateBuffer().
+    *************************************************************************/
+    public class rbfcalcbuffer : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public rbfcalcbuffer()
+        {
+            _innerobj = new rbf.rbfcalcbuffer();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new rbfcalcbuffer((rbf.rbfcalcbuffer)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private rbf.rbfcalcbuffer _innerobj;
+        public rbf.rbfcalcbuffer innerobj { get { return _innerobj; } }
+        public rbfcalcbuffer(rbf.rbfcalcbuffer obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    RBF model.
+
+    Never try to directly work with fields of this object - always use  ALGLIB
+    functions to use this object.
+    *************************************************************************/
+    public class rbfmodel : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public rbfmodel()
+        {
+            _innerobj = new rbf.rbfmodel();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new rbfmodel((rbf.rbfmodel)_innerobj.make_copy());
+        }
+    
+        //
+        // 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.
+
+    Fields which are set by modern RBF solvers (hierarchical):
+    * RMSError          -   root-mean-square error; NAN for old solvers (ML, QNN)
+    * MaxError          -   maximum error; NAN for old solvers (ML, QNN)
+    *************************************************************************/
+    public class rbfreport : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public double rmserror { get { return _innerobj.rmserror; } set { _innerobj.rmserror = value; } }
+        public double maxerror { get { return _innerobj.maxerror; } set { _innerobj.maxerror = value; } }
+        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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new rbfreport((rbf.rbfreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+        s.sstart_str();
+        rbf.rbfserialize(s, obj.innerobj, null);
+        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, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function serializes data structure to stream.
+    
+    Data stream generated by this function is same as  string  representation
+    generated  by  string  version  of  serializer - alphanumeric characters,
+    dots, underscores, minus signs, which are grouped into words separated by
+    spaces and CR+LF.
+    
+    We recommend you to read comments on string version of serializer to find
+    out more about serialization of AlGLIB objects.
+    *************************************************************************/
+    public static void rbfserialize(rbfmodel obj, System.IO.Stream stream_out)
+    {
+        alglib.serializer s = new alglib.serializer();
+        s.alloc_start();
+        rbf.rbfalloc(s, obj.innerobj, null);
+        s.sstart_stream(stream_out);
+        rbf.rbfserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+
+
+    /*************************************************************************
+    This function unserializes data structure from stream.
+    *************************************************************************/
+    public static void rbfunserialize(System.IO.Stream stream_in, out rbfmodel obj)
+    {
+        alglib.serializer s = new alglib.serializer();
+        obj = new rbfmodel();
+        s.ustart_stream(stream_in);
+        rbf.rbfunserialize(s, obj.innerobj, null);
+        s.stop();
+    }
+    
+    /*************************************************************************
+    This function creates RBF  model  for  a  scalar (NY=1)  or  vector (NY>1)
+    function in a NX-dimensional space (NX>=1).
+
+    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) or rbfsetpointsandscales().
+    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 tweaks algorithm properties with  rbfsetalgohierarchical()  method
+       (or chooses one of the legacy algorithms - 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.
+
+    IMPORTANT: we recommend you to use latest model construction  algorithm  -
+               hierarchical RBFs, which is activated by rbfsetalgohierarchical()
+               function. This algorithm is the fastest one, and  most  memory-
+               efficient.
+               However,  it  is  incompatible  with older versions  of  ALGLIB
+               (pre-3.11). So, if you serialize hierarchical model,  you  will
+               be unable to load it in pre-3.11 ALGLIB. Other model types (QNN
+               and RBF-ML) are still backward-compatible.
+
+    INPUT PARAMETERS:
+        NX      -   dimension of the space, NX>=1
+        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. Some additional memory
+            is allocated during model construction, but most of this memory is
+            freed after model coefficients  are  calculated.  Amount  of  this
+            additional memory depends on model  construction  algorithm  being
+            used.
+
+    NOTE 2: prior to ALGLIB version 3.11, RBF models supported  only  NX=2  or
+            NX=3. Any  attempt  to  create  single-dimensional  or  more  than
+            3-dimensional RBF model resulted in exception.
+
+            ALGLIB 3.11 supports any NX>0, but models created with  NX!=2  and
+            NX!=3 are incompatible with (a) older versions of ALGLIB, (b)  old
+            model construction algorithms (QNN or RBF-ML).
+
+            So, if you create a model with NX=2 or NX=3,  then,  depending  on
+            specific  model construction algorithm being chosen, you will (QNN
+            and RBF-ML) or will not (HierarchicalRBF) get backward compatibility
+            with older versions of ALGLIB. You have a choice here.
+
+            However, if you create a model with NX neither 2 nor 3,  you  have
+            no backward compatibility from the start, and you  are  forced  to
+            use hierarchical RBFs and ALGLIB 3.11 or later.
+
+      -- ALGLIB --
+         Copyright 13.12.2011, 20.06.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfcreate(int nx, int ny, out rbfmodel s)
+    {
+        s = new rbfmodel();
+        rbf.rbfcreate(nx, ny, s.innerobj, null);
+    }
+    
+    public static void rbfcreate(int nx, int ny, out rbfmodel s, alglib.xparams _params)
+    {
+        s = new rbfmodel();
+        rbf.rbfcreate(nx, ny, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function creates buffer  structure  which  can  be  used  to  perform
+    parallel  RBF  model  evaluations  (with  one  RBF  model  instance  being
+    used from multiple threads, as long as  different  threads  use  different
+    instances of buffer).
+
+    This buffer object can be used with  rbftscalcbuf()  function  (here  "ts"
+    stands for "thread-safe", "buf" is a suffix which denotes  function  which
+    reuses previously allocated output space).
+
+    How to use it:
+    * create RBF model structure with rbfcreate()
+    * load data, tune parameters
+    * call rbfbuildmodel()
+    * call rbfcreatecalcbuffer(), once per thread working with RBF model  (you
+      should call this function only AFTER call to rbfbuildmodel(), see  below
+      for more information)
+    * call rbftscalcbuf() from different threads,  with  each  thread  working
+      with its own copy of buffer object.
+
+    INPUT PARAMETERS
+        S           -   RBF model
+
+    OUTPUT PARAMETERS
+        Buf         -   external buffer.
+
+
+    IMPORTANT: buffer object should be used only with  RBF model object  which
+               was used to initialize buffer. Any attempt to use buffer   with
+               different object is dangerous - you may  get  memory  violation
+               error because sizes of internal arrays do not fit to dimensions
+               of RBF structure.
+
+    IMPORTANT: you  should  call  this function only for model which was built
+               with rbfbuildmodel() function, after successful  invocation  of
+               rbfbuildmodel().  Sizes   of   some   internal  structures  are
+               determined only after model is built, so buffer object  created
+               before model  construction  stage  will  be  useless  (and  any
+               attempt to use it will result in exception).
+
+      -- ALGLIB --
+         Copyright 02.04.2016 by Sergey Bochkanov
+    *************************************************************************/
+    public static void rbfcreatecalcbuffer(rbfmodel s, out rbfcalcbuffer buf)
+    {
+        buf = new rbfcalcbuffer();
+        rbf.rbfcreatecalcbuffer(s.innerobj, buf.innerobj, null);
+    }
+    
+    public static void rbfcreatecalcbuffer(rbfmodel s, out rbfcalcbuffer buf, alglib.xparams _params)
+    {
+        buf = new rbfcalcbuffer();
+        rbf.rbfcreatecalcbuffer(s.innerobj, buf.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+    IMPORTANT: ALGLIB version 3.11 and later allows you to specify  a  set  of
+               per-dimension scales. Interpolation radii are multiplied by the
+               scale vector. It may be useful if you have mixed spatio-temporal
+               data (say, a set of 3D slices recorded at different times).
+               You should call rbfsetpointsandscales() function  to  use  this
+               feature.
+
+    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
+                    specified, 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: dataset added by this function is not saved during model serialization.
+          MODEL ITSELF is serialized, but data used to build it are not.
+
+          So, if you 1) add dataset to  empty  RBF  model,  2)  serialize  and
+          unserialize it, then you will get an empty RBF model with no dataset
+          being attached.
+
+          From the other side, if you call rbfbuildmodel() between (1) and (2),
+          then after (2) you will get your fully constructed RBF model  -  but
+          again with no dataset attached, so subsequent calls to rbfbuildmodel()
+          will produce empty model.
+
+
+      -- ALGLIB --
+         Copyright 13.12.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfsetpoints(rbfmodel s, double[,] xy, int n)
+    {
+    
+        rbf.rbfsetpoints(s.innerobj, xy, n, null);
+    }
+    
+    public static void rbfsetpoints(rbfmodel s, double[,] xy, int n, alglib.xparams _params)
+    {
+    
+        rbf.rbfsetpoints(s.innerobj, xy, n, _params);
+    }
+            
+    public static void rbfsetpoints(rbfmodel s, double[,] xy)
+    {
+        int n;
+    
+    
+        n = ap.rows(xy);
+        rbf.rbfsetpoints(s.innerobj, xy, n, null);
+    
+        return;
+    }
+            
+    public static void rbfsetpoints(rbfmodel s, double[,] xy, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.rows(xy);
+        rbf.rbfsetpoints(s.innerobj, xy, n, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function adds dataset and a vector of per-dimension scales.
+
+    It may be useful if you have mixed spatio-temporal data - say, a set of 3D
+    slices recorded at different times. Such data typically require  different
+    RBF radii for spatial and temporal dimensions. ALGLIB solves this  problem
+    by specifying single RBF radius, which is (optionally) multiplied  by  the
+    scale vector.
+
+    This function overrides results of the previous calls, i.e. multiple calls
+    of this function will result in only the last set being added.
+
+    IMPORTANT: only HierarchicalRBF algorithm can work with scaled points. So,
+               using this function results in RBF models which can be used  in
+               ALGLIB 3.11 or later. Previous versions of the library will  be
+               unable  to unserialize models produced by HierarchicalRBF algo.
+
+               Any attempt to use this function with RBF-ML or QNN  algorithms
+               will result  in  -3  error  code   being   returned  (incorrect
+               algorithm).
+
+    INPUT PARAMETERS:
+        R       -   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
+                    specified, in  this  case  only leading [N,NX+NY] elements
+                    will be used.
+        N       -   number of points in the dataset
+        S       -   array[NX], scale vector, S[i]>0.
+
+    After you've added dataset and (optionally) tuned algorithm  settings  you
+    should call rbfbuildmodel() in order to build a model for you.
+
+    NOTE: dataset added by this function is not saved during model serialization.
+          MODEL ITSELF is serialized, but data used to build it are not.
+
+          So, if you 1) add dataset to  empty  RBF  model,  2)  serialize  and
+          unserialize it, then you will get an empty RBF model with no dataset
+          being attached.
+
+          From the other side, if you call rbfbuildmodel() between (1) and (2),
+          then after (2) you will get your fully constructed RBF model  -  but
+          again with no dataset attached, so subsequent calls to rbfbuildmodel()
+          will produce empty model.
+
+
+      -- ALGLIB --
+         Copyright 20.06.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfsetpointsandscales(rbfmodel r, double[,] xy, int n, double[] s)
+    {
+    
+        rbf.rbfsetpointsandscales(r.innerobj, xy, n, s, null);
+    }
+    
+    public static void rbfsetpointsandscales(rbfmodel r, double[,] xy, int n, double[] s, alglib.xparams _params)
+    {
+    
+        rbf.rbfsetpointsandscales(r.innerobj, xy, n, s, _params);
+    }
+            
+    public static void rbfsetpointsandscales(rbfmodel r, double[,] xy, double[] s)
+    {
+        int n;
+    
+    
+        n = ap.rows(xy);
+        rbf.rbfsetpointsandscales(r.innerobj, xy, n, s, null);
+    
+        return;
+    }
+            
+    public static void rbfsetpointsandscales(rbfmodel r, double[,] xy, double[] s, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.rows(xy);
+        rbf.rbfsetpointsandscales(r.innerobj, xy, n, s, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    DEPRECATED:since version 3.11 ALGLIB includes new RBF  model  construction
+               algorithm, Hierarchical  RBF.  This  algorithm  is  faster  and
+               requires less memory than QNN and RBF-ML. It is especially good
+               for large-scale interpolation problems. So, we recommend you to
+               consider Hierarchical RBF as default option.
+
+    ==========================================================================
+
+    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, null);
+    }
+    
+    public static void rbfsetalgoqnn(rbfmodel s, double q, double z, alglib.xparams _params)
+    {
+    
+        rbf.rbfsetalgoqnn(s.innerobj, q, z, _params);
+    }
+            
+    public static void rbfsetalgoqnn(rbfmodel s)
+    {
+        double q;
+        double z;
+    
+    
+        q = 1.0;
+        z = 5.0;
+        rbf.rbfsetalgoqnn(s.innerobj, q, z, null);
+    
+        return;
+    }
+            
+    public static void rbfsetalgoqnn(rbfmodel s, alglib.xparams _params)
+    {
+        double q;
+        double z;
+    
+    
+        q = 1.0;
+        z = 5.0;
+        rbf.rbfsetalgoqnn(s.innerobj, q, z, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    DEPRECATED:since version 3.11 ALGLIB includes new RBF  model  construction
+               algorithm, Hierarchical  RBF.  This  algorithm  is  faster  and
+               requires less memory than QNN and RBF-ML. It is especially good
+               for large-scale interpolation problems. So, we recommend you to
+               consider Hierarchical RBF as default option.
+
+    ==========================================================================
+
+    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, null);
+    }
+    
+    public static void rbfsetalgomultilayer(rbfmodel s, double rbase, int nlayers, double lambdav, alglib.xparams _params)
+    {
+    
+        rbf.rbfsetalgomultilayer(s.innerobj, rbase, nlayers, lambdav, _params);
+    }
+            
+    public static void rbfsetalgomultilayer(rbfmodel s, double rbase, int nlayers)
+    {
+        double lambdav;
+    
+    
+        lambdav = 0.01;
+        rbf.rbfsetalgomultilayer(s.innerobj, rbase, nlayers, lambdav, null);
+    
+        return;
+    }
+            
+    public static void rbfsetalgomultilayer(rbfmodel s, double rbase, int nlayers, alglib.xparams _params)
+    {
+        double lambdav;
+    
+    
+        lambdav = 0.01;
+        rbf.rbfsetalgomultilayer(s.innerobj, rbase, nlayers, lambdav, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This  function  sets  RBF interpolation algorithm. ALGLIB supports several
+    RBF algorithms with different properties.
+
+    This  algorithm is called Hierarchical RBF. It  similar  to  its  previous
+    incarnation, RBF-ML, i.e.  it  also  builds  a  sequence  of  models  with
+    decreasing radii. However, it uses more economical way of  building  upper
+    layers (ones with large radii), which results in faster model construction
+    and evaluation, as well as smaller memory footprint during construction.
+
+    This algorithm has following important features:
+    * ability to handle millions of points
+    * controllable smoothing via nonlinearity penalization
+    * support for NX-dimensional models with NX=1 or NX>3 (unlike QNN or RBF-ML)
+    * support for specification of per-dimensional  radii  via  scale  vector,
+      which is set by means of rbfsetpointsandscales() function. This  feature
+      is useful if you solve  spatio-temporal  interpolation  problems,  where
+      different radii are required for spatial and temporal dimensions.
+
+    Running times are roughly proportional to:
+    * N*log(N)*NLayers - for model construction
+    * N*NLayers - for model evaluation
+    You may see that running time does not depend on search radius  or  points
+    density, just on number of layers in the hierarchy.
+
+    IMPORTANT: this model construction algorithm was introduced in ALGLIB 3.11
+               and  produces  models  which  are  INCOMPATIBLE  with  previous
+               versions of ALGLIB. You can  not  unserialize  models  produced
+               with this function in ALGLIB 3.10 or earlier.
+
+    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.
+        LambdaNS-   >=0, nonlinearity penalty coefficient, negative values are
+                    not allowed. This parameter adds controllable smoothing to
+                    the problem, which may reduce noise. Specification of non-
+                    zero lambda means that in addition to fitting error solver
+                    will  also  minimize   LambdaNS*|S''(x)|^2  (appropriately
+                    generalized to multiple dimensions.
+
+                    Specification of exactly zero value means that no  penalty
+                    is added  (we  do  not  even  evaluate  matrix  of  second
+                    derivatives which is necessary for smoothing).
+
+                    Calculation of nonlinearity penalty is costly - it results
+                    in  several-fold  increase  of  model  construction  time.
+                    Evaluation time remains the same.
+
+                    Optimal  lambda  is  problem-dependent and requires  trial
+                    and  error.  Good  value to  start  from  is  1e-5...1e-6,
+                    which corresponds to slightly noticeable smoothing  of the
+                    function.  Value  1e-2  usually  means  that  quite  heavy
+                    smoothing is applied.
+
+    TUNING ALGORITHM
+
+    In order to use this algorithm you have to choose three parameters:
+    * initial radius RBase
+    * number of layers in the model NLayers
+    * penalty coefficient LambdaNS
+
+    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 (or specify
+    non-zero LambdaNS).
+
+    TYPICAL ERRORS
+
+    1. 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.
+
+    2. 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.
+
+      -- ALGLIB --
+         Copyright 20.06.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfsetalgohierarchical(rbfmodel s, double rbase, int nlayers, double lambdans)
+    {
+    
+        rbf.rbfsetalgohierarchical(s.innerobj, rbase, nlayers, lambdans, null);
+    }
+    
+    public static void rbfsetalgohierarchical(rbfmodel s, double rbase, int nlayers, double lambdans, alglib.xparams _params)
+    {
+    
+        rbf.rbfsetalgohierarchical(s.innerobj, rbase, nlayers, lambdans, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rbfsetlinterm(rbfmodel s, alglib.xparams _params)
+    {
+    
+        rbf.rbfsetlinterm(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rbfsetconstterm(rbfmodel s, alglib.xparams _params)
+    {
+    
+        rbf.rbfsetconstterm(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rbfsetzeroterm(rbfmodel s, alglib.xparams _params)
+    {
+    
+        rbf.rbfsetzeroterm(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function sets basis function type, which can be:
+    * 0 for classic Gaussian
+    * 1 for fast and compact bell-like basis function, which  becomes  exactly
+      zero at distance equal to 3*R (default option).
+
+    INPUT PARAMETERS:
+        S       -   RBF model, initialized by RBFCreate() call
+        BF      -   basis function type:
+                    * 0 - classic Gaussian
+                    * 1 - fast and compact one
+
+      -- ALGLIB --
+         Copyright 01.02.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfsetv2bf(rbfmodel s, int bf)
+    {
+    
+        rbf.rbfsetv2bf(s.innerobj, bf, null);
+    }
+    
+    public static void rbfsetv2bf(rbfmodel s, int bf, alglib.xparams _params)
+    {
+    
+        rbf.rbfsetv2bf(s.innerobj, bf, _params);
+    }
+    
+    /*************************************************************************
+    This function sets stopping criteria of the underlying linear  solver  for
+    hierarchical (version 2) RBF constructor.
+
+    INPUT PARAMETERS:
+        S       -   RBF model, initialized by RBFCreate() call
+        MaxIts  -   this criterion will stop algorithm after MaxIts iterations.
+                    Typically a few hundreds iterations is required,  with 400
+                    being a good default value to start experimentation.
+                    Zero value means that default value will be selected.
+
+      -- ALGLIB --
+         Copyright 01.02.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfsetv2its(rbfmodel s, int maxits)
+    {
+    
+        rbf.rbfsetv2its(s.innerobj, maxits, null);
+    }
+    
+    public static void rbfsetv2its(rbfmodel s, int maxits, alglib.xparams _params)
+    {
+    
+        rbf.rbfsetv2its(s.innerobj, maxits, _params);
+    }
+    
+    /*************************************************************************
+    This function sets support radius parameter  of  hierarchical  (version 2)
+    RBF constructor.
+
+    Hierarchical RBF model achieves great speed-up  by removing from the model
+    excessive (too dense) nodes. Say, if you have RBF radius equal to 1 meter,
+    and two nodes are just 1 millimeter apart, you  may  remove  one  of  them
+    without reducing model quality.
+
+    Support radius parameter is used to justify which points need removal, and
+    which do not. If two points are less than  SUPPORT_R*CUR_RADIUS  units  of
+    distance apart, one of them is removed from the model. The larger  support
+    radius  is, the faster model  construction  AND  evaluation are.  However,
+    too large values result in "bumpy" models.
+
+    INPUT PARAMETERS:
+        S       -   RBF model, initialized by RBFCreate() call
+        R       -   support radius coefficient, >=0.
+                    Recommended values are [0.1,0.4] range, with 0.1 being
+                    default value.
+
+      -- ALGLIB --
+         Copyright 01.02.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfsetv2supportr(rbfmodel s, double r)
+    {
+    
+        rbf.rbfsetv2supportr(s.innerobj, r, null);
+    }
+    
+    public static void rbfsetv2supportr(rbfmodel s, double r, alglib.xparams _params)
+    {
+    
+        rbf.rbfsetv2supportr(s.innerobj, r, _params);
+    }
+    
+    /*************************************************************************
+    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;  only  QNN  returns  this
+                             error   code, other  algorithms  can  handle non-
+                             distinct nodes.
+                      * -4 - nonconvergence of the internal SVD solver
+                      * -3   incorrect model construction algorithm was chosen:
+                             QNN or RBF-ML, combined with one of the incompatible
+                             features - NX=1 or NX>3; points with per-dimension
+                             scales.
+                      *  1 - successful termination
+                      *  8 - a termination request was submitted via
+                             rbfrequesttermination() function.
+
+                    Fields which are set only by modern RBF solvers (hierarchical
+                    or nonnegative; older solvers like QNN and ML initialize these
+                    fields by NANs):
+                    * rep.rmserror - root-mean-square error at nodes
+                    * rep.maxerror - maximum error at nodes
+
+                    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, null);
+    }
+    
+    public static void rbfbuildmodel(rbfmodel s, out rbfreport rep, alglib.xparams _params)
+    {
+        rep = new rbfreport();
+        rbf.rbfbuildmodel(s.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates values of the RBF model in the given point.
+
+    IMPORTANT: this function works only with modern  (hierarchical)  RBFs.  It
+               can not be used with legacy (version 1) RBFs because older  RBF
+               code does not support 1-dimensional models.
+
+    This function should be used when we have NY=1 (scalar function) and  NX=1
+    (1-dimensional space). If you have 3-dimensional space, use rbfcalc3(). If
+    you  have  2-dimensional  space,  use  rbfcalc3().  If  you  have  general
+    situation (NX-dimensional space, NY-dimensional function)  you  should use
+    generic rbfcalc().
+
+    If you want to perform parallel model evaluation  from  multiple  threads,
+    use rbftscalcbuf() with per-thread buffer object.
+
+    This function returns 0.0 when:
+    * model is not initialized
+    * NX<>1
+    * NY<>1
+
+    INPUT PARAMETERS:
+        S       -   RBF model
+        X0      -   X-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 rbfcalc1(rbfmodel s, double x0)
+    {
+    
+        return rbf.rbfcalc1(s.innerobj, x0, null);
+    }
+    
+    public static double rbfcalc1(rbfmodel s, double x0, alglib.xparams _params)
+    {
+    
+        return rbf.rbfcalc1(s.innerobj, x0, _params);
+    }
+    
+    /*************************************************************************
+    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 generic rbfcalc().
+
+    If  you  want  to  calculate  function  values  many times, consider using
+    rbfgridcalc2v(), which is far more efficient than many subsequent calls to
+    rbfcalc2().
+
+    If you want to perform parallel model evaluation  from  multiple  threads,
+    use rbftscalcbuf() with per-thread buffer object.
+
+    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)
+    {
+    
+        return rbf.rbfcalc2(s.innerobj, x0, x1, null);
+    }
+    
+    public static double rbfcalc2(rbfmodel s, double x0, double x1, alglib.xparams _params)
+    {
+    
+        return rbf.rbfcalc2(s.innerobj, x0, x1, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates value 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 generic rbfcalc().
+
+    If  you  want  to  calculate  function  values  many times, consider using
+    rbfgridcalc3v(), which is far more efficient than many subsequent calls to
+    rbfcalc3().
+
+    If you want to perform parallel model evaluation  from  multiple  threads,
+    use rbftscalcbuf() with per-thread buffer object.
+
+    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)
+    {
+    
+        return rbf.rbfcalc3(s.innerobj, x0, x1, x2, null);
+    }
+    
+    public static double rbfcalc3(rbfmodel s, double x0, double x1, double x2, alglib.xparams _params)
+    {
+    
+        return rbf.rbfcalc3(s.innerobj, x0, x1, x2, _params);
+    }
+    
+    /*************************************************************************
+    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().
+
+    If you want to perform parallel model evaluation  from  multiple  threads,
+    use rbftscalcbuf() with per-thread buffer object.
+
+    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, null);
+    }
+    
+    public static void rbfcalc(rbfmodel s, double[] x, out double[] y, alglib.xparams _params)
+    {
+        y = new double[0];
+        rbf.rbfcalc(s.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+    If you want to perform parallel model evaluation  from  multiple  threads,
+    use rbftscalcbuf() with per-thread buffer object.
+
+    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, null);
+    }
+    
+    public static void rbfcalcbuf(rbfmodel s, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        rbf.rbfcalcbuf(s.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates values of the RBF model at the given point, using
+    external  buffer  object  (internal  temporaries  of  RBF  model  are  not
+    modified).
+
+    This function allows to use same RBF model object  in  different  threads,
+    assuming  that  different   threads  use  different  instances  of  buffer
+    structure.
+
+    INPUT PARAMETERS:
+        S       -   RBF model, may be shared between different threads
+        Buf     -   buffer object created for this particular instance of  RBF
+                    model with rbfcreatecalcbuffer().
+        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 rbftscalcbuf(rbfmodel s, rbfcalcbuffer buf, double[] x, ref double[] y)
+    {
+    
+        rbf.rbftscalcbuf(s.innerobj, buf.innerobj, x, ref y, null);
+    }
+    
+    public static void rbftscalcbuf(rbfmodel s, rbfcalcbuffer buf, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        rbf.rbftscalcbuf(s.innerobj, buf.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This is legacy function for gridded calculation of RBF model.
+
+    It is superseded by rbfgridcalc2v() and  rbfgridcalc2vsubset()  functions.
+
+      -- 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, null);
+    }
+    
+    public static void rbfgridcalc2(rbfmodel s, double[] x0, int n0, double[] x1, int n1, out double[,] y, alglib.xparams _params)
+    {
+        y = new double[0,0];
+        rbf.rbfgridcalc2(s.innerobj, x0, n0, x1, n1, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates values of the RBF  model  at  the  regular  grid,
+    which  has  N0*N1 points, with Point[I,J] = (X0[I], X1[J]).  Vector-valued
+    RBF models are supported.
+
+    This function returns 0.0 when:
+    * model is not initialized
+    * NX<>2
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    NOTE: Parallel  processing  is  implemented only for modern (hierarchical)
+          RBFs. Legacy version 1 RBFs (created  by  QNN  or  RBF-ML) are still
+          processed serially.
+
+    INPUT PARAMETERS:
+        S       -   RBF model, used in read-only mode, can be  shared  between
+                    multiple   invocations  of  this  function  from  multiple
+                    threads.
+
+        X0      -   array of grid nodes, first coordinates, array[N0].
+                    Must be ordered by ascending. Exception is generated
+                    if the array is not correctly ordered.
+        N0      -   grid size (number of nodes) in the first dimension
+
+        X1      -   array of grid nodes, second coordinates, array[N1]
+                    Must be ordered by ascending. Exception is generated
+                    if the array is not correctly ordered.
+        N1      -   grid size (number of nodes) in the second dimension
+
+    OUTPUT PARAMETERS:
+        Y       -   function values, array[NY*N0*N1], where NY is a  number of
+                    "output" vector values (this  function   supports  vector-
+                    valued RBF models). Y is out-variable and  is  reallocated
+                    by this function.
+                    Y[K+NY*(I0+I1*N0)]=F_k(X0[I0],X1[I1]), for:
+                    *  K=0...NY-1
+                    * I0=0...N0-1
+                    * I1=0...N1-1
+
+    NOTE: this function supports weakly ordered grid nodes, i.e. you may  have
+          X[i]=X[i+1] for some i. It does  not  provide  you  any  performance
+          benefits  due  to   duplication  of  points,  just  convenience  and
+          flexibility.
+
+    NOTE: this  function  is  re-entrant,  i.e.  you  may  use  same  rbfmodel
+          structure in multiple threads calling  this function  for  different
+          grids.
+
+    NOTE: if you need function values on some subset  of  regular  grid, which
+          may be described as "several compact and  dense  islands",  you  may
+          use rbfgridcalc2vsubset().
+
+      -- ALGLIB --
+         Copyright 27.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfgridcalc2v(rbfmodel s, double[] x0, int n0, double[] x1, int n1, out double[] y)
+    {
+        y = new double[0];
+        rbf.rbfgridcalc2v(s.innerobj, x0, n0, x1, n1, ref y, null);
+    }
+    
+    public static void rbfgridcalc2v(rbfmodel s, double[] x0, int n0, double[] x1, int n1, out double[] y, alglib.xparams _params)
+    {
+        y = new double[0];
+        rbf.rbfgridcalc2v(s.innerobj, x0, n0, x1, n1, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates values of the RBF model at some subset of regular
+    grid:
+    * grid has N0*N1 points, with Point[I,J] = (X0[I], X1[J])
+    * only values at some subset of this grid are required
+    Vector-valued RBF models are supported.
+
+    This function returns 0.0 when:
+    * model is not initialized
+    * NX<>2
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    NOTE: Parallel  processing  is  implemented only for modern (hierarchical)
+          RBFs. Legacy version 1 RBFs (created  by  QNN  or  RBF-ML) are still
+          processed serially.
+
+    INPUT PARAMETERS:
+        S       -   RBF model, used in read-only mode, can be  shared  between
+                    multiple   invocations  of  this  function  from  multiple
+                    threads.
+
+        X0      -   array of grid nodes, first coordinates, array[N0].
+                    Must be ordered by ascending. Exception is generated
+                    if the array is not correctly ordered.
+        N0      -   grid size (number of nodes) in the first dimension
+
+        X1      -   array of grid nodes, second coordinates, array[N1]
+                    Must be ordered by ascending. Exception is generated
+                    if the array is not correctly ordered.
+        N1      -   grid size (number of nodes) in the second dimension
+
+        FlagY   -   array[N0*N1]:
+                    * Y[I0+I1*N0] corresponds to node (X0[I0],X1[I1])
+                    * it is a "bitmap" array which contains  False  for  nodes
+                      which are NOT calculated, and True for nodes  which  are
+                      required.
+
+    OUTPUT PARAMETERS:
+        Y       -   function values, array[NY*N0*N1*N2], where NY is a  number
+                    of "output" vector values (this function  supports vector-
+                    valued RBF models):
+                    * Y[K+NY*(I0+I1*N0)]=F_k(X0[I0],X1[I1]),
+                      for K=0...NY-1, I0=0...N0-1, I1=0...N1-1.
+                    * elements of Y[] which correspond  to  FlagY[]=True   are
+                      loaded by model values (which may be  exactly  zero  for
+                      some nodes).
+                    * elements of Y[] which correspond to FlagY[]=False MAY be
+                      initialized by zeros OR may be calculated. This function
+                      processes  grid  as  a  hierarchy  of  nested blocks and
+                      micro-rows. If just one element of micro-row is required,
+                      entire micro-row (up to 8 nodes in the current  version,
+                      but no promises) is calculated.
+
+    NOTE: this function supports weakly ordered grid nodes, i.e. you may  have
+          X[i]=X[i+1] for some i. It does  not  provide  you  any  performance
+          benefits  due  to   duplication  of  points,  just  convenience  and
+          flexibility.
+
+    NOTE: this  function  is  re-entrant,  i.e.  you  may  use  same  rbfmodel
+          structure in multiple threads calling  this function  for  different
+          grids.
+
+      -- ALGLIB --
+         Copyright 04.03.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfgridcalc2vsubset(rbfmodel s, double[] x0, int n0, double[] x1, int n1, bool[] flagy, out double[] y)
+    {
+        y = new double[0];
+        rbf.rbfgridcalc2vsubset(s.innerobj, x0, n0, x1, n1, flagy, ref y, null);
+    }
+    
+    public static void rbfgridcalc2vsubset(rbfmodel s, double[] x0, int n0, double[] x1, int n1, bool[] flagy, out double[] y, alglib.xparams _params)
+    {
+        y = new double[0];
+        rbf.rbfgridcalc2vsubset(s.innerobj, x0, n0, x1, n1, flagy, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates values of the RBF  model  at  the  regular  grid,
+    which  has  N0*N1*N2  points,  with  Point[I,J,K] = (X0[I], X1[J], X2[K]).
+    Vector-valued RBF models are supported.
+
+    This function returns 0.0 when:
+    * model is not initialized
+    * NX<>3
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    NOTE: Parallel  processing  is  implemented only for modern (hierarchical)
+          RBFs. Legacy version 1 RBFs (created  by  QNN  or  RBF-ML) are still
+          processed serially.
+
+    INPUT PARAMETERS:
+        S       -   RBF model, used in read-only mode, can be  shared  between
+                    multiple   invocations  of  this  function  from  multiple
+                    threads.
+
+        X0      -   array of grid nodes, first coordinates, array[N0].
+                    Must be ordered by ascending. Exception is generated
+                    if the array is not correctly ordered.
+        N0      -   grid size (number of nodes) in the first dimension
+
+        X1      -   array of grid nodes, second coordinates, array[N1]
+                    Must be ordered by ascending. Exception is generated
+                    if the array is not correctly ordered.
+        N1      -   grid size (number of nodes) in the second dimension
+
+        X2      -   array of grid nodes, third coordinates, array[N2]
+                    Must be ordered by ascending. Exception is generated
+                    if the array is not correctly ordered.
+        N2      -   grid size (number of nodes) in the third dimension
+
+    OUTPUT PARAMETERS:
+        Y       -   function values, array[NY*N0*N1*N2], where NY is a  number
+                    of "output" vector values (this function  supports vector-
+                    valued RBF models). Y is out-variable and  is  reallocated
+                    by this function.
+                    Y[K+NY*(I0+I1*N0+I2*N0*N1)]=F_k(X0[I0],X1[I1],X2[I2]), for:
+                    *  K=0...NY-1
+                    * I0=0...N0-1
+                    * I1=0...N1-1
+                    * I2=0...N2-1
+
+    NOTE: this function supports weakly ordered grid nodes, i.e. you may  have
+          X[i]=X[i+1] for some i. It does  not  provide  you  any  performance
+          benefits  due  to   duplication  of  points,  just  convenience  and
+          flexibility.
+
+    NOTE: this  function  is  re-entrant,  i.e.  you  may  use  same  rbfmodel
+          structure in multiple threads calling  this function  for  different
+          grids.
+
+    NOTE: if you need function values on some subset  of  regular  grid, which
+          may be described as "several compact and  dense  islands",  you  may
+          use rbfgridcalc3vsubset().
+
+      -- ALGLIB --
+         Copyright 04.03.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfgridcalc3v(rbfmodel s, double[] x0, int n0, double[] x1, int n1, double[] x2, int n2, out double[] y)
+    {
+        y = new double[0];
+        rbf.rbfgridcalc3v(s.innerobj, x0, n0, x1, n1, x2, n2, ref y, null);
+    }
+    
+    public static void rbfgridcalc3v(rbfmodel s, double[] x0, int n0, double[] x1, int n1, double[] x2, int n2, out double[] y, alglib.xparams _params)
+    {
+        y = new double[0];
+        rbf.rbfgridcalc3v(s.innerobj, x0, n0, x1, n1, x2, n2, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates values of the RBF model at some subset of regular
+    grid:
+    * grid has N0*N1*N2 points, with Point[I,J,K] = (X0[I], X1[J], X2[K])
+    * only values at some subset of this grid are required
+    Vector-valued RBF models are supported.
+
+    This function returns 0.0 when:
+    * model is not initialized
+    * NX<>3
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    NOTE: Parallel  processing  is  implemented only for modern (hierarchical)
+          RBFs. Legacy version 1 RBFs (created  by  QNN  or  RBF-ML) are still
+          processed serially.
+
+    INPUT PARAMETERS:
+        S       -   RBF model, used in read-only mode, can be  shared  between
+                    multiple   invocations  of  this  function  from  multiple
+                    threads.
+
+        X0      -   array of grid nodes, first coordinates, array[N0].
+                    Must be ordered by ascending. Exception is generated
+                    if the array is not correctly ordered.
+        N0      -   grid size (number of nodes) in the first dimension
+
+        X1      -   array of grid nodes, second coordinates, array[N1]
+                    Must be ordered by ascending. Exception is generated
+                    if the array is not correctly ordered.
+        N1      -   grid size (number of nodes) in the second dimension
+
+        X2      -   array of grid nodes, third coordinates, array[N2]
+                    Must be ordered by ascending. Exception is generated
+                    if the array is not correctly ordered.
+        N2      -   grid size (number of nodes) in the third dimension
+
+        FlagY   -   array[N0*N1*N2]:
+                    * Y[I0+I1*N0+I2*N0*N1] corresponds to node (X0[I0],X1[I1],X2[I2])
+                    * it is a "bitmap" array which contains  False  for  nodes
+                      which are NOT calculated, and True for nodes  which  are
+                      required.
+
+    OUTPUT PARAMETERS:
+        Y       -   function values, array[NY*N0*N1*N2], where NY is a  number
+                    of "output" vector values (this function  supports vector-
+                    valued RBF models):
+                    * Y[K+NY*(I0+I1*N0+I2*N0*N1)]=F_k(X0[I0],X1[I1],X2[I2]),
+                      for K=0...NY-1, I0=0...N0-1, I1=0...N1-1, I2=0...N2-1.
+                    * elements of Y[] which correspond  to  FlagY[]=True   are
+                      loaded by model values (which may be  exactly  zero  for
+                      some nodes).
+                    * elements of Y[] which correspond to FlagY[]=False MAY be
+                      initialized by zeros OR may be calculated. This function
+                      processes  grid  as  a  hierarchy  of  nested blocks and
+                      micro-rows. If just one element of micro-row is required,
+                      entire micro-row (up to 8 nodes in the current  version,
+                      but no promises) is calculated.
+
+    NOTE: this function supports weakly ordered grid nodes, i.e. you may  have
+          X[i]=X[i+1] for some i. It does  not  provide  you  any  performance
+          benefits  due  to   duplication  of  points,  just  convenience  and
+          flexibility.
+
+    NOTE: this  function  is  re-entrant,  i.e.  you  may  use  same  rbfmodel
+          structure in multiple threads calling  this function  for  different
+          grids.
+
+      -- ALGLIB --
+         Copyright 04.03.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfgridcalc3vsubset(rbfmodel s, double[] x0, int n0, double[] x1, int n1, double[] x2, int n2, bool[] flagy, out double[] y)
+    {
+        y = new double[0];
+        rbf.rbfgridcalc3vsubset(s.innerobj, x0, n0, x1, n1, x2, n2, flagy, ref y, null);
+    }
+    
+    public static void rbfgridcalc3vsubset(rbfmodel s, double[] x0, int n0, double[] x1, int n1, double[] x2, int n2, bool[] flagy, out double[] y, alglib.xparams _params)
+    {
+        y = new double[0];
+        rbf.rbfgridcalc3vsubset(s.innerobj, x0, n0, x1, n1, x2, n2, flagy, ref y, _params);
+    }
+    
+    /*************************************************************************
+    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
+                    For ModelVersion=1:
+                    * last column       - radius, same for all dimensions of
+                                          the function being modelled
+                    For ModelVersion=2:
+                    * last NX columns   - radii, one per dimension
+        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.
+        ModelVersion-version of the RBF model:
+                    * 1 - for models created by QNN and RBF-ML algorithms,
+                      compatible with ALGLIB 3.10 or earlier.
+                    * 2 - for models created by HierarchicalRBF, requires
+                      ALGLIB 3.11 or later
+
+      -- 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, out int modelversion)
+    {
+        nx = 0;
+        ny = 0;
+        xwr = new double[0,0];
+        nc = 0;
+        v = new double[0,0];
+        modelversion = 0;
+        rbf.rbfunpack(s.innerobj, ref nx, ref ny, ref xwr, ref nc, ref v, ref modelversion, null);
+    }
+    
+    public static void rbfunpack(rbfmodel s, out int nx, out int ny, out double[,] xwr, out int nc, out double[,] v, out int modelversion, alglib.xparams _params)
+    {
+        nx = 0;
+        ny = 0;
+        xwr = new double[0,0];
+        nc = 0;
+        v = new double[0,0];
+        modelversion = 0;
+        rbf.rbfunpack(s.innerobj, ref nx, ref ny, ref xwr, ref nc, ref v, ref modelversion, _params);
+    }
+    
+    /*************************************************************************
+    This function returns model version.
+
+    INPUT PARAMETERS:
+        S       -   RBF model
+
+    RESULT:
+        * 1 - for models created by QNN and RBF-ML algorithms,
+          compatible with ALGLIB 3.10 or earlier.
+        * 2 - for models created by HierarchicalRBF, requires
+          ALGLIB 3.11 or later
+
+      -- ALGLIB --
+         Copyright 06.07.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static int rbfgetmodelversion(rbfmodel s)
+    {
+    
+        return rbf.rbfgetmodelversion(s.innerobj, null);
+    }
+    
+    public static int rbfgetmodelversion(rbfmodel s, alglib.xparams _params)
+    {
+    
+        return rbf.rbfgetmodelversion(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function is used to peek into hierarchical RBF  construction  process
+    from  some  other  thread  and  get current progress indicator. It returns
+    value in [0,1].
+
+    IMPORTANT: only HRBFs (hierarchical RBFs) support  peeking  into  progress
+               indicator. Legacy RBF-ML and RBF-QNN do  not  support  it.  You
+               will always get 0 value.
+
+    INPUT PARAMETERS:
+        S           -   RBF model object
+
+    RESULT:
+        progress value, in [0,1]
+
+      -- ALGLIB --
+         Copyright 17.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static double rbfpeekprogress(rbfmodel s)
+    {
+    
+        return rbf.rbfpeekprogress(s.innerobj, null);
+    }
+    
+    public static double rbfpeekprogress(rbfmodel s, alglib.xparams _params)
+    {
+    
+        return rbf.rbfpeekprogress(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function  is  used  to  submit  a  request  for  termination  of  the
+    hierarchical RBF construction process from some other thread.  As  result,
+    RBF construction is terminated smoothly (with proper deallocation  of  all
+    necessary resources) and resultant model is filled by zeros.
+
+    A rep.terminationtype=8 will be returned upon receiving such request.
+
+    IMPORTANT: only  HRBFs  (hierarchical  RBFs) support termination requests.
+               Legacy RBF-ML and RBF-QNN do not  support  it.  An  attempt  to
+               terminate their construction will be ignored.
+
+    IMPORTANT: termination request flag is cleared when the model construction
+               starts. Thus, any pre-construction termination requests will be
+               silently ignored - only ones submitted AFTER  construction  has
+               actually began will be handled.
+
+    INPUT PARAMETERS:
+        S           -   RBF model object
+
+      -- ALGLIB --
+         Copyright 17.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rbfrequesttermination(rbfmodel s)
+    {
+    
+        rbf.rbfrequesttermination(s.innerobj, null);
+    }
+    
+    public static void rbfrequesttermination(rbfmodel s, alglib.xparams _params)
+    {
+    
+        rbf.rbfrequesttermination(s.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+    
+    /*************************************************************************
+    This function is left for backward compatibility.
+    Use fitspheremc() instead.
+
+
+      -- ALGLIB --
+         Copyright 14.04.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void nsfitspheremcc(double[,] xy, int npoints, int nx, out double[] cx, out double rhi)
+    {
+        cx = new double[0];
+        rhi = 0;
+        intcomp.nsfitspheremcc(xy, npoints, nx, ref cx, ref rhi, null);
+    }
+    
+    public static void nsfitspheremcc(double[,] xy, int npoints, int nx, out double[] cx, out double rhi, alglib.xparams _params)
+    {
+        cx = new double[0];
+        rhi = 0;
+        intcomp.nsfitspheremcc(xy, npoints, nx, ref cx, ref rhi, _params);
+    }
+    
+    /*************************************************************************
+    This function is left for backward compatibility.
+    Use fitspheremi() instead.
+
+      -- ALGLIB --
+         Copyright 14.04.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void nsfitspheremic(double[,] xy, int npoints, int nx, out double[] cx, out double rlo)
+    {
+        cx = new double[0];
+        rlo = 0;
+        intcomp.nsfitspheremic(xy, npoints, nx, ref cx, ref rlo, null);
+    }
+    
+    public static void nsfitspheremic(double[,] xy, int npoints, int nx, out double[] cx, out double rlo, alglib.xparams _params)
+    {
+        cx = new double[0];
+        rlo = 0;
+        intcomp.nsfitspheremic(xy, npoints, nx, ref cx, ref rlo, _params);
+    }
+    
+    /*************************************************************************
+    This function is left for backward compatibility.
+    Use fitspheremz() instead.
+
+      -- ALGLIB --
+         Copyright 14.04.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void nsfitspheremzc(double[,] xy, int npoints, int nx, out double[] cx, out double rlo, out double rhi)
+    {
+        cx = new double[0];
+        rlo = 0;
+        rhi = 0;
+        intcomp.nsfitspheremzc(xy, npoints, nx, ref cx, ref rlo, ref rhi, null);
+    }
+    
+    public static void nsfitspheremzc(double[,] xy, int npoints, int nx, out double[] cx, out double rlo, out double rhi, alglib.xparams _params)
+    {
+        cx = new double[0];
+        rlo = 0;
+        rhi = 0;
+        intcomp.nsfitspheremzc(xy, npoints, nx, ref cx, ref rlo, ref rhi, _params);
+    }
+    
+    /*************************************************************************
+    This function is left for backward compatibility.
+    Use fitspherex() instead.
+
+      -- ALGLIB --
+         Copyright 14.04.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void nsfitspherex(double[,] xy, int npoints, int nx, int problemtype, double epsx, int aulits, double penalty, out double[] cx, out double rlo, out double rhi)
+    {
+        cx = new double[0];
+        rlo = 0;
+        rhi = 0;
+        intcomp.nsfitspherex(xy, npoints, nx, problemtype, epsx, aulits, penalty, ref cx, ref rlo, ref rhi, null);
+    }
+    
+    public static void nsfitspherex(double[,] xy, int npoints, int nx, int problemtype, double epsx, int aulits, double penalty, out double[] cx, out double rlo, out double rhi, alglib.xparams _params)
+    {
+        cx = new double[0];
+        rlo = 0;
+        rhi = 0;
+        intcomp.nsfitspherex(xy, npoints, nx, problemtype, epsx, aulits, penalty, ref cx, ref rlo, ref rhi, _params);
+    }
+
+}
+public partial class alglib
+{
+    public class idw
+    {
+        /*************************************************************************
+        Buffer  object  which  is  used  to  perform  evaluation  requests  in  the
+        multithreaded mode (multiple threads working with same IDW object).
+
+        This object should be created with idwcreatecalcbuffer().
+        *************************************************************************/
+        public class idwcalcbuffer : apobject
+        {
+            public double[] x;
+            public double[] y;
+            public double[] tsyw;
+            public double[] tsw;
+            public double[,] tsxy;
+            public double[] tsdist;
+            public nearestneighbor.kdtreerequestbuffer requestbuffer;
+            public idwcalcbuffer()
+            {
+                init();
+            }
+            public override void init()
+            {
+                x = new double[0];
+                y = new double[0];
+                tsyw = new double[0];
+                tsw = new double[0];
+                tsxy = new double[0,0];
+                tsdist = new double[0];
+                requestbuffer = new nearestneighbor.kdtreerequestbuffer();
+            }
+            public override alglib.apobject make_copy()
+            {
+                idwcalcbuffer _result = new idwcalcbuffer();
+                _result.x = (double[])x.Clone();
+                _result.y = (double[])y.Clone();
+                _result.tsyw = (double[])tsyw.Clone();
+                _result.tsw = (double[])tsw.Clone();
+                _result.tsxy = (double[,])tsxy.Clone();
+                _result.tsdist = (double[])tsdist.Clone();
+                _result.requestbuffer = (nearestneighbor.kdtreerequestbuffer)requestbuffer.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        IDW (Inverse Distance Weighting) model object.
+        *************************************************************************/
+        public class idwmodel : apobject
+        {
+            public int nx;
+            public int ny;
+            public double[] globalprior;
+            public int algotype;
+            public int nlayers;
+            public double r0;
+            public double rdecay;
+            public double lambda0;
+            public double lambdalast;
+            public double lambdadecay;
+            public double shepardp;
+            public nearestneighbor.kdtree tree;
+            public int npoints;
+            public double[] shepardxy;
+            public idwcalcbuffer buffer;
+            public idwmodel()
+            {
+                init();
+            }
+            public override void init()
+            {
+                globalprior = new double[0];
+                tree = new nearestneighbor.kdtree();
+                shepardxy = new double[0];
+                buffer = new idwcalcbuffer();
+            }
+            public override alglib.apobject make_copy()
+            {
+                idwmodel _result = new idwmodel();
+                _result.nx = nx;
+                _result.ny = ny;
+                _result.globalprior = (double[])globalprior.Clone();
+                _result.algotype = algotype;
+                _result.nlayers = nlayers;
+                _result.r0 = r0;
+                _result.rdecay = rdecay;
+                _result.lambda0 = lambda0;
+                _result.lambdalast = lambdalast;
+                _result.lambdadecay = lambdadecay;
+                _result.shepardp = shepardp;
+                _result.tree = (nearestneighbor.kdtree)tree.make_copy();
+                _result.npoints = npoints;
+                _result.shepardxy = (double[])shepardxy.Clone();
+                _result.buffer = (idwcalcbuffer)buffer.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Builder object used to generate IDW (Inverse Distance Weighting) model.
+        *************************************************************************/
+        public class idwbuilder : apobject
+        {
+            public int priortermtype;
+            public double[] priortermval;
+            public int algotype;
+            public int nlayers;
+            public double r0;
+            public double rdecay;
+            public double lambda0;
+            public double lambdalast;
+            public double lambdadecay;
+            public double shepardp;
+            public double[] xy;
+            public int npoints;
+            public int nx;
+            public int ny;
+            public double[,] tmpxy;
+            public double[,] tmplayers;
+            public int[] tmptags;
+            public double[] tmpdist;
+            public double[] tmpx;
+            public double[] tmpwy;
+            public double[] tmpw;
+            public nearestneighbor.kdtree tmptree;
+            public double[] tmpmean;
+            public idwbuilder()
+            {
+                init();
+            }
+            public override void init()
+            {
+                priortermval = new double[0];
+                xy = new double[0];
+                tmpxy = new double[0,0];
+                tmplayers = new double[0,0];
+                tmptags = new int[0];
+                tmpdist = new double[0];
+                tmpx = new double[0];
+                tmpwy = new double[0];
+                tmpw = new double[0];
+                tmptree = new nearestneighbor.kdtree();
+                tmpmean = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                idwbuilder _result = new idwbuilder();
+                _result.priortermtype = priortermtype;
+                _result.priortermval = (double[])priortermval.Clone();
+                _result.algotype = algotype;
+                _result.nlayers = nlayers;
+                _result.r0 = r0;
+                _result.rdecay = rdecay;
+                _result.lambda0 = lambda0;
+                _result.lambdalast = lambdalast;
+                _result.lambdadecay = lambdadecay;
+                _result.shepardp = shepardp;
+                _result.xy = (double[])xy.Clone();
+                _result.npoints = npoints;
+                _result.nx = nx;
+                _result.ny = ny;
+                _result.tmpxy = (double[,])tmpxy.Clone();
+                _result.tmplayers = (double[,])tmplayers.Clone();
+                _result.tmptags = (int[])tmptags.Clone();
+                _result.tmpdist = (double[])tmpdist.Clone();
+                _result.tmpx = (double[])tmpx.Clone();
+                _result.tmpwy = (double[])tmpwy.Clone();
+                _result.tmpw = (double[])tmpw.Clone();
+                _result.tmptree = (nearestneighbor.kdtree)tmptree.make_copy();
+                _result.tmpmean = (double[])tmpmean.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        IDW fitting report:
+            rmserror        RMS error
+            avgerror        average error
+            maxerror        maximum error
+            r2              coefficient of determination,  R-squared, 1-RSS/TSS
+        *************************************************************************/
+        public class idwreport : apobject
+        {
+            public double rmserror;
+            public double avgerror;
+            public double maxerror;
+            public double r2;
+            public idwreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                idwreport _result = new idwreport();
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.maxerror = maxerror;
+                _result.r2 = r2;
+                return _result;
+            }
+        };
+
+
+
+
+        public const double w0 = 1.0;
+        public const double meps = 1.0E-50;
+        public const int defaultnlayers = 16;
+        public const double defaultlambda0 = 0.3333;
+
+
+        /*************************************************************************
+        This function creates buffer  structure  which  can  be  used  to  perform
+        parallel  IDW  model  evaluations  (with  one  IDW  model  instance  being
+        used from multiple threads, as long as  different  threads  use  different
+        instances of buffer).
+
+        This buffer object can be used with  idwtscalcbuf()  function  (here  "ts"
+        stands for "thread-safe", "buf" is a suffix which denotes  function  which
+        reuses previously allocated output space).
+
+        How to use it:
+        * create IDW model structure or load it from file
+        * call idwcreatecalcbuffer(), once per thread working with IDW model  (you
+          should call this function only AFTER model initialization, see below for
+          more information)
+        * call idwtscalcbuf() from different threads,  with  each  thread  working
+          with its own copy of buffer object.
+
+        INPUT PARAMETERS
+            S           -   IDW model
+
+        OUTPUT PARAMETERS
+            Buf         -   external buffer.
+            
+            
+        IMPORTANT: buffer object should be used only with  IDW model object  which
+                   was used to initialize buffer. Any attempt to use buffer   with
+                   different object is dangerous - you may  get  memory  violation
+                   error because sizes of internal arrays do not fit to dimensions
+                   of the IDW structure.
+                   
+        IMPORTANT: you  should  call  this function only for model which was built
+                   with model builder (or unserialized from file). Sizes  of  some
+                   internal structures are determined only after model  is  built,
+                   so buffer object created before model construction  stage  will
+                   be useless (and any attempt to use it will result in exception).
+
+          -- ALGLIB --
+             Copyright 22.10.2018 by Sergey Bochkanov
+        *************************************************************************/
+        public static void idwcreatecalcbuffer(idwmodel s,
+            idwcalcbuffer buf,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(s.nx>=1, "IDWCreateCalcBuffer: integrity check failed");
+            alglib.ap.assert(s.ny>=1, "IDWCreateCalcBuffer: integrity check failed");
+            alglib.ap.assert(s.nlayers>=0, "IDWCreateCalcBuffer: integrity check failed");
+            alglib.ap.assert(s.algotype>=0, "IDWCreateCalcBuffer: integrity check failed");
+            if( s.nlayers>=1 && s.algotype!=0 )
+            {
+                nearestneighbor.kdtreecreaterequestbuffer(s.tree, buf.requestbuffer, _params);
+            }
+            apserv.rvectorsetlengthatleast(ref buf.x, s.nx, _params);
+            apserv.rvectorsetlengthatleast(ref buf.y, s.ny, _params);
+            apserv.rvectorsetlengthatleast(ref buf.tsyw, s.ny*Math.Max(s.nlayers, 1), _params);
+            apserv.rvectorsetlengthatleast(ref buf.tsw, Math.Max(s.nlayers, 1), _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine creates builder object used  to  generate IDW  model  from
+        irregularly sampled (scattered) dataset.  Multidimensional  scalar/vector-
+        -valued are supported.
+
+        Builder object is used to fit model to data as follows:
+        * builder object is created with idwbuildercreate() function
+        * dataset is added with idwbuildersetpoints() function
+        * one of the modern IDW algorithms is chosen with either:
+          * idwbuildersetalgomstab()            - Multilayer STABilized algorithm (interpolation)
+          Alternatively, one of the textbook algorithms can be chosen (not recommended):
+          * idwbuildersetalgotextbookshepard()  - textbook Shepard algorithm
+          * idwbuildersetalgotextbookmodshepard()-textbook modified Shepard algorithm
+        * finally, model construction is performed with idwfit() function.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            NX  -   dimensionality of the argument, NX>=1
+            NY  -   dimensionality of the function being modeled, NY>=1;
+                    NY=1 corresponds to classic scalar function, NY>=1 corresponds
+                    to vector-valued function.
+            
+        OUTPUT PARAMETERS:
+            State-  builder object
+
+          -- ALGLIB PROJECT --
+             Copyright 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwbuildercreate(int nx,
+            int ny,
+            idwbuilder state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(nx>=1, "IDWBuilderCreate: NX<=0");
+            alglib.ap.assert(ny>=1, "IDWBuilderCreate: NY<=0");
+            
+            //
+            // We choose reasonable defaults for the algorithm:
+            // * MSTAB algorithm
+            // * 12 layers
+            // * default radius
+            // * default Lambda0
+            //
+            state.algotype = 2;
+            state.priortermtype = 2;
+            apserv.rvectorsetlengthatleast(ref state.priortermval, ny, _params);
+            state.nlayers = defaultnlayers;
+            state.r0 = 0;
+            state.rdecay = 0.5;
+            state.lambda0 = defaultlambda0;
+            state.lambdalast = 0;
+            state.lambdadecay = 1.0;
+            
+            //
+            // Other parameters, not used but initialized
+            //
+            state.shepardp = 0;
+            
+            //
+            // Initial dataset is empty
+            //
+            state.npoints = 0;
+            state.nx = nx;
+            state.ny = ny;
+        }
+
+
+        /*************************************************************************
+        This function changes number of layers used by IDW-MSTAB algorithm.
+
+        The more layers you have, the finer details can  be  reproduced  with  IDW
+        model. The less layers you have, the less memory and CPU time is  consumed
+        by the model.
+
+        Memory consumption grows linearly with layers count,  running  time  grows
+        sub-linearly.
+
+        The default number of layers is 16, which allows you to reproduce  details
+        at distance down to SRad/65536. You will rarely need to change it.
+
+        INPUT PARAMETERS:
+            State   -   builder object
+            NLayers -   NLayers>=1, the number of layers used by the model.
+
+          -- ALGLIB --
+             Copyright 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwbuildersetnlayers(idwbuilder state,
+            int nlayers,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(nlayers>=1, "IDWBuilderSetNLayers: N<1");
+            state.nlayers = nlayers;
+        }
+
+
+        /*************************************************************************
+        This function adds dataset to the builder object.
+
+        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:
+            State   -   builder object
+            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 
+                        specified, in  this  case  only leading [N,NX+NY] elements 
+                        will be used.
+            N       -   number of points in the dataset, N>=0.
+
+          -- ALGLIB --
+             Copyright 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwbuildersetpoints(idwbuilder state,
+            double[,] xy,
+            int n,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int ew = 0;
+
+            alglib.ap.assert(n>=0, "IDWBuilderSetPoints: N<0");
+            alglib.ap.assert(alglib.ap.rows(xy)>=n, "IDWBuilderSetPoints: Rows(XY)<N");
+            alglib.ap.assert(n==0 || alglib.ap.cols(xy)>=state.nx+state.ny, "IDWBuilderSetPoints: Cols(XY)<NX+NY");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, n, state.nx+state.ny, _params), "IDWBuilderSetPoints: XY contains infinite or NaN values!");
+            state.npoints = n;
+            ew = state.nx+state.ny;
+            apserv.rvectorsetlengthatleast(ref state.xy, n*ew, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=ew-1; j++)
+                {
+                    state.xy[i*ew+j] = xy[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets IDW model  construction  algorithm  to  the  Multilayer
+        Stabilized IDW method (IDW-MSTAB), a  latest  incarnation  of  the inverse
+        distance weighting interpolation which fixes shortcomings of  the original
+        and modified Shepard's variants.
+
+        The distinctive features of IDW-MSTAB are:
+        1) exact interpolation  is  pursued  (as  opposed  to  fitting  and  noise
+           suppression)
+        2) improved robustness when compared with that of other algorithms:
+           * MSTAB shows almost no strange  fitting  artifacts  like  ripples  and
+             sharp spikes (unlike N-dimensional splines and HRBFs)
+           * MSTAB does not return function values far from the  interval  spanned
+             by the dataset; say, if all your points have |f|<=1, you  can be sure
+             that model value won't deviate too much from [-1,+1]
+        3) good model construction time competing with that of HRBFs  and  bicubic
+           splines
+        4) ability to work with any number of dimensions, starting from NX=1
+
+        The drawbacks of IDW-MSTAB (and all IDW algorithms in general) are:
+        1) dependence of the model evaluation time on the search radius
+        2) bad extrapolation properties, models built by this method  are  usually
+           conservative in their predictions
+
+        Thus, IDW-MSTAB is  a  good  "default"  option  if  you  want  to  perform
+        scattered multidimensional interpolation. Although it has  its  drawbacks,
+        it is easy to use and robust, which makes it a good first step.
+
+
+        INPUT PARAMETERS:
+            State   -   builder object
+            SRad    -   initial search radius, SRad>0 is required. A model  value
+                        is obtained by "smart" averaging  of  the  dataset  points
+                        within search radius.
+
+        NOTE 1: IDW interpolation can  correctly  handle  ANY  dataset,  including
+                datasets with non-distinct points. In case non-distinct points are
+                found, an average value for this point will be calculated.
+                
+        NOTE 2: the memory requirements for model storage are O(NPoints*NLayers).
+                The model construction needs twice as much memory as model storage.
+          
+        NOTE 3: by default 16 IDW layers are built which is enough for most cases.
+                You can change this parameter with idwbuildersetnlayers()  method.
+                Larger values may be necessary if you need to reproduce  extrafine
+                details at distances smaller than SRad/65536.  Smaller value   may
+                be necessary if you have to save memory and  computing  time,  and
+                ready to sacrifice some model quality.
+
+
+        ALGORITHM DESCRIPTION
+          
+        ALGLIB implementation of IDW is somewhat similar to the modified Shepard's
+        method (one with search radius R) but overcomes several of its  drawbacks,
+        namely:
+        1) a tendency to show stepwise behavior for uniform datasets
+        2) a tendency to show terrible interpolation properties for highly
+           nonuniform datasets which often arise in geospatial tasks
+          (function values are densely sampled across multiple separated
+          "tracks")
+
+        IDW-MSTAB method performs several passes over dataset and builds a sequence
+        of progressively refined IDW models  (layers),  which starts from one with
+        largest search radius SRad  and continues to smaller  search  radii  until
+        required number of  layers  is  built.  Highest  layers  reproduce  global
+        behavior of the target function at larger distances  whilst  lower  layers
+        reproduce fine details at smaller distances.
+
+        Each layer is an IDW model built with following modifications:
+        * weights go to zero when distance approach to the current search radius
+        * an additional regularizing term is added to the distance: w=1/(d^2+lambda)
+        * an additional fictional term with unit weight and zero function value is
+          added in order to promote continuity  properties  at  the  isolated  and
+          boundary points
+          
+        By default, 16 layers is built, which is enough for most  cases.  You  can
+        change this parameter with idwbuildersetnlayers() method.
+           
+          -- ALGLIB --
+             Copyright 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwbuildersetalgomstab(idwbuilder state,
+            double srad,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(srad), "IDWBuilderSetAlgoMSTAB: SRad is not finite");
+            alglib.ap.assert((double)(srad)>(double)(0), "IDWBuilderSetAlgoMSTAB: SRad<=0");
+            
+            //
+            // Set algorithm
+            //
+            state.algotype = 2;
+            
+            //
+            // Set options
+            //
+            state.r0 = srad;
+            state.rdecay = 0.5;
+            state.lambda0 = defaultlambda0;
+            state.lambdalast = 0;
+            state.lambdadecay = 1.0;
+        }
+
+
+        /*************************************************************************
+        This function sets  IDW  model  construction  algorithm  to  the  textbook
+        Shepard's algorithm with custom (user-specified) power parameter.
+
+        IMPORTANT: we do NOT recommend using textbook IDW algorithms because  they
+                   have terrible interpolation properties. Use MSTAB in all cases.
+
+        INPUT PARAMETERS:
+            State   -   builder object
+            P       -   power parameter, P>0; good value to start with is 2.0
+
+        NOTE 1: IDW interpolation can  correctly  handle  ANY  dataset,  including
+                datasets with non-distinct points. In case non-distinct points are
+                found, an average value for this point will be calculated.
+           
+          -- ALGLIB --
+             Copyright 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwbuildersetalgotextbookshepard(idwbuilder state,
+            double p,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(p), "IDWBuilderSetAlgoShepard: P is not finite");
+            alglib.ap.assert((double)(p)>(double)(0), "IDWBuilderSetAlgoShepard: P<=0");
+            
+            //
+            // Set algorithm and options
+            //
+            state.algotype = 0;
+            state.shepardp = p;
+        }
+
+
+        /*************************************************************************
+        This function sets  IDW  model  construction  algorithm  to the 'textbook'
+        modified Shepard's algorithm with user-specified search radius.
+
+        IMPORTANT: we do NOT recommend using textbook IDW algorithms because  they
+                   have terrible interpolation properties. Use MSTAB in all cases.
+
+        INPUT PARAMETERS:
+            State   -   builder object
+            R       -   search radius
+
+        NOTE 1: IDW interpolation can  correctly  handle  ANY  dataset,  including
+                datasets with non-distinct points. In case non-distinct points are
+                found, an average value for this point will be calculated.
+           
+          -- ALGLIB --
+             Copyright 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwbuildersetalgotextbookmodshepard(idwbuilder state,
+            double r,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(r), "IDWBuilderSetAlgoModShepard: R is not finite");
+            alglib.ap.assert((double)(r)>(double)(0), "IDWBuilderSetAlgoModShepard: R<=0");
+            
+            //
+            // Set algorithm and options
+            //
+            state.algotype = 1;
+            state.r0 = r;
+        }
+
+
+        /*************************************************************************
+        This function sets prior term (model value at infinity) as  user-specified
+        value.
+
+        INPUT PARAMETERS:
+            S       -   spline builder
+            V       -   value for user-defined prior
+            
+        NOTE: for vector-valued models all components of the prior are set to same
+              user-specified value
+
+          -- ALGLIB --
+             Copyright 29.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwbuildersetuserterm(idwbuilder state,
+            double v,
+            alglib.xparams _params)
+        {
+            int j = 0;
+
+            alglib.ap.assert(math.isfinite(v), "IDWBuilderSetUserTerm: infinite/NAN value passed");
+            state.priortermtype = 0;
+            for(j=0; j<=state.ny-1; j++)
+            {
+                state.priortermval[j] = v;
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets constant prior term (model value at infinity).
+
+        Constant prior term is determined as mean value over dataset.
+
+        INPUT PARAMETERS:
+            S       -   spline builder
+
+          -- ALGLIB --
+             Copyright 29.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwbuildersetconstterm(idwbuilder state,
+            alglib.xparams _params)
+        {
+            state.priortermtype = 2;
+        }
+
+
+        /*************************************************************************
+        This function sets zero prior term (model value at infinity).
+
+        INPUT PARAMETERS:
+            S       -   spline builder
+
+          -- ALGLIB --
+             Copyright 29.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwbuildersetzeroterm(idwbuilder state,
+            alglib.xparams _params)
+        {
+            state.priortermtype = 3;
+        }
+
+
+        /*************************************************************************
+        IDW interpolation: scalar target, 1-dimensional argument
+
+        NOTE: this function modifies internal temporaries of the  IDW  model, thus
+              IT IS NOT  THREAD-SAFE!  If  you  want  to  perform  parallel  model
+              evaluation from the multiple threads, use idwtscalcbuf()  with  per-
+              thread buffer object. 
+              
+        INPUT PARAMETERS:
+            S   -   IDW interpolant built with IDW builder
+            X0  -   argument value
+
+        Result:
+            IDW interpolant S(X0)
+
+          -- ALGLIB --
+             Copyright 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static double idwcalc1(idwmodel s,
+            double x0,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(s.nx==1, "IDWCalc1: S.NX<>1");
+            alglib.ap.assert(s.ny==1, "IDWCalc1: S.NY<>1");
+            alglib.ap.assert(math.isfinite(x0), "IDWCalc1: X0 is INF or NAN");
+            s.buffer.x[0] = x0;
+            idwtscalcbuf(s, s.buffer, s.buffer.x, ref s.buffer.y, _params);
+            result = s.buffer.y[0];
+            return result;
+        }
+
+
+        /*************************************************************************
+        IDW interpolation: scalar target, 2-dimensional argument
+
+        NOTE: this function modifies internal temporaries of the  IDW  model, thus
+              IT IS NOT  THREAD-SAFE!  If  you  want  to  perform  parallel  model
+              evaluation from the multiple threads, use idwtscalcbuf()  with  per-
+              thread buffer object. 
+              
+        INPUT PARAMETERS:
+            S       -   IDW interpolant built with IDW builder
+            X0, X1  -   argument value
+
+        Result:
+            IDW interpolant S(X0,X1)
+
+          -- ALGLIB --
+             Copyright 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static double idwcalc2(idwmodel s,
+            double x0,
+            double x1,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(s.nx==2, "IDWCalc2: S.NX<>2");
+            alglib.ap.assert(s.ny==1, "IDWCalc2: S.NY<>1");
+            alglib.ap.assert(math.isfinite(x0), "IDWCalc2: X0 is INF or NAN");
+            alglib.ap.assert(math.isfinite(x1), "IDWCalc2: X1 is INF or NAN");
+            s.buffer.x[0] = x0;
+            s.buffer.x[1] = x1;
+            idwtscalcbuf(s, s.buffer, s.buffer.x, ref s.buffer.y, _params);
+            result = s.buffer.y[0];
+            return result;
+        }
+
+
+        /*************************************************************************
+        IDW interpolation: scalar target, 3-dimensional argument
+
+        NOTE: this function modifies internal temporaries of the  IDW  model, thus
+              IT IS NOT  THREAD-SAFE!  If  you  want  to  perform  parallel  model
+              evaluation from the multiple threads, use idwtscalcbuf()  with  per-
+              thread buffer object. 
+
+        INPUT PARAMETERS:
+            S       -   IDW interpolant built with IDW builder
+            X0,X1,X2-   argument value
+
+        Result:
+            IDW interpolant S(X0,X1,X2)
+
+          -- ALGLIB --
+             Copyright 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static double idwcalc3(idwmodel s,
+            double x0,
+            double x1,
+            double x2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(s.nx==3, "IDWCalc3: S.NX<>3");
+            alglib.ap.assert(s.ny==1, "IDWCalc3: S.NY<>1");
+            alglib.ap.assert(math.isfinite(x0), "IDWCalc3: X0 is INF or NAN");
+            alglib.ap.assert(math.isfinite(x1), "IDWCalc3: X1 is INF or NAN");
+            alglib.ap.assert(math.isfinite(x2), "IDWCalc3: X2 is INF or NAN");
+            s.buffer.x[0] = x0;
+            s.buffer.x[1] = x1;
+            s.buffer.x[2] = x2;
+            idwtscalcbuf(s, s.buffer, s.buffer.x, ref s.buffer.y, _params);
+            result = s.buffer.y[0];
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the IDW 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  idwcalc1(),
+        idwcalc2() or idwcalc3().
+
+        NOTE: this function modifies internal temporaries of the  IDW  model, thus
+              IT IS NOT  THREAD-SAFE!  If  you  want  to  perform  parallel  model
+              evaluation from the multiple threads, use idwtscalcbuf()  with  per-
+              thread buffer object. 
+              
+        INPUT PARAMETERS:
+            S       -   IDW 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 will  be
+                        reallocated after call to this function. In case you  want
+                        to reuse previously allocated Y, you may use idwcalcbuf(),
+                        which reallocates Y only when it is too small.
+
+          -- ALGLIB --
+             Copyright 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwcalc(idwmodel s,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            y = new double[0];
+
+            idwtscalcbuf(s, s.buffer, x, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the IDW model at the given point.
+
+        Same as idwcalc(), but does not reallocate Y when in is large enough to 
+        store function values.
+
+        NOTE: this function modifies internal temporaries of the  IDW  model, thus
+              IT IS NOT  THREAD-SAFE!  If  you  want  to  perform  parallel  model
+              evaluation from the multiple threads, use idwtscalcbuf()  with  per-
+              thread buffer object. 
+              
+        INPUT PARAMETERS:
+            S       -   IDW 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 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwcalcbuf(idwmodel s,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            idwtscalcbuf(s, s.buffer, x, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the IDW model at the given point, using
+        external  buffer  object  (internal  temporaries  of  IDW  model  are  not
+        modified).
+
+        This function allows to use same IDW model object  in  different  threads,
+        assuming  that  different   threads  use different instances of the buffer
+        structure.
+
+        INPUT PARAMETERS:
+            S       -   IDW model, may be shared between different threads
+            Buf     -   buffer object created for this particular instance of  IDW
+                        model with idwcreatecalcbuffer().
+            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 idwtscalcbuf(idwmodel s,
+            idwcalcbuffer buf,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int ew = 0;
+            int k = 0;
+            int layeridx = 0;
+            int nx = 0;
+            int ny = 0;
+            int npoints = 0;
+            double v = 0;
+            double vv = 0;
+            double f = 0;
+            double p = 0;
+            double r = 0;
+            double eps = 0;
+            double lambdacur = 0;
+            double lambdadecay = 0;
+            double invrdecay = 0;
+            double invr = 0;
+            bool fastcalcpossible = new bool();
+            double wf0 = 0;
+            double ws0 = 0;
+            double wf1 = 0;
+            double ws1 = 0;
+
+            nx = s.nx;
+            ny = s.ny;
+            alglib.ap.assert(alglib.ap.len(x)>=nx, "IDWTsCalcBuf: Length(X)<NX");
+            alglib.ap.assert(apserv.isfinitevector(x, nx, _params), "IDWTsCalcBuf: X contains infinite or NaN values");
+            
+            //
+            // Avoid spurious compiler warnings
+            //
+            wf0 = 0;
+            ws0 = 0;
+            wf1 = 0;
+            ws1 = 0;
+            
+            //
+            // Allocate output
+            //
+            if( alglib.ap.len(y)<ny )
+            {
+                y = new double[ny];
+            }
+            
+            //
+            // Quick exit for NLayers=0 (no dataset)
+            //
+            if( s.nlayers==0 )
+            {
+                for(j=0; j<=ny-1; j++)
+                {
+                    y[j] = s.globalprior[j];
+                }
+                return;
+            }
+            
+            //
+            // Textbook Shepard's method
+            //
+            if( s.algotype==0 )
+            {
+                npoints = s.npoints;
+                alglib.ap.assert(npoints>0, "IDWTsCalcBuf: integrity check failed");
+                eps = 1.0E-50;
+                ew = nx+ny;
+                p = s.shepardp;
+                for(j=0; j<=ny-1; j++)
+                {
+                    y[j] = 0;
+                    buf.tsyw[j] = eps;
+                }
+                for(i=0; i<=npoints-1; i++)
+                {
+                    
+                    //
+                    // Compute squared distance
+                    //
+                    v = 0;
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        vv = s.shepardxy[i*ew+j]-x[j];
+                        v = v+vv*vv;
+                    }
+                    
+                    //
+                    // Compute weight (with small regularizing addition)
+                    //
+                    v = Math.Pow(v, p*0.5);
+                    v = 1/(eps+v);
+                    
+                    //
+                    // Accumulate
+                    //
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        y[j] = y[j]+v*s.shepardxy[i*ew+nx+j];
+                        buf.tsyw[j] = buf.tsyw[j]+v;
+                    }
+                }
+                for(j=0; j<=ny-1; j++)
+                {
+                    y[j] = y[j]/buf.tsyw[j]+s.globalprior[j];
+                }
+                return;
+            }
+            
+            //
+            // Textbook modified Shepard's method
+            //
+            if( s.algotype==1 )
+            {
+                eps = 1.0E-50;
+                r = s.r0;
+                for(j=0; j<=ny-1; j++)
+                {
+                    y[j] = 0;
+                    buf.tsyw[j] = eps;
+                }
+                k = nearestneighbor.kdtreetsqueryrnn(s.tree, buf.requestbuffer, x, r, true, _params);
+                nearestneighbor.kdtreetsqueryresultsxy(s.tree, buf.requestbuffer, ref buf.tsxy, _params);
+                nearestneighbor.kdtreetsqueryresultsdistances(s.tree, buf.requestbuffer, ref buf.tsdist, _params);
+                for(i=0; i<=k-1; i++)
+                {
+                    v = buf.tsdist[i];
+                    v = (r-v)/(r*v+eps);
+                    v = v*v;
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        y[j] = y[j]+v*buf.tsxy[i,nx+j];
+                        buf.tsyw[j] = buf.tsyw[j]+v;
+                    }
+                }
+                for(j=0; j<=ny-1; j++)
+                {
+                    y[j] = y[j]/buf.tsyw[j]+s.globalprior[j];
+                }
+                return;
+            }
+            
+            //
+            // MSTAB
+            //
+            if( s.algotype==2 )
+            {
+                alglib.ap.assert((double)(w0)==(double)(1), "IDWTsCalcBuf: unexpected W0, integrity check failed");
+                invrdecay = 1/s.rdecay;
+                invr = 1/s.r0;
+                lambdadecay = s.lambdadecay;
+                fastcalcpossible = (ny==1 && s.nlayers>=3) && (double)(lambdadecay)==(double)(1);
+                if( fastcalcpossible )
+                {
+                    
+                    //
+                    // Important special case, NY=1, no lambda-decay,
+                    // we can perform optimized fast evaluation
+                    //
+                    wf0 = 0;
+                    ws0 = w0;
+                    wf1 = 0;
+                    ws1 = w0;
+                    for(j=0; j<=s.nlayers-1; j++)
+                    {
+                        buf.tsyw[j] = 0;
+                        buf.tsw[j] = w0;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Setup variables for generic evaluation path
+                    //
+                    for(j=0; j<=ny*s.nlayers-1; j++)
+                    {
+                        buf.tsyw[j] = 0;
+                    }
+                    for(j=0; j<=s.nlayers-1; j++)
+                    {
+                        buf.tsw[j] = w0;
+                    }
+                }
+                k = nearestneighbor.kdtreetsqueryrnnu(s.tree, buf.requestbuffer, x, s.r0, true, _params);
+                nearestneighbor.kdtreetsqueryresultsxy(s.tree, buf.requestbuffer, ref buf.tsxy, _params);
+                nearestneighbor.kdtreetsqueryresultsdistances(s.tree, buf.requestbuffer, ref buf.tsdist, _params);
+                for(i=0; i<=k-1; i++)
+                {
+                    lambdacur = s.lambda0;
+                    vv = buf.tsdist[i]*invr;
+                    if( fastcalcpossible )
+                    {
+                        
+                        //
+                        // Important special case, fast evaluation possible
+                        //
+                        v = vv*vv;
+                        v = (1-v)*(1-v)/(v+lambdacur);
+                        f = buf.tsxy[i,nx+0];
+                        wf0 = wf0+v*f;
+                        ws0 = ws0+v;
+                        vv = vv*invrdecay;
+                        if( vv>=1.0 )
+                        {
+                            continue;
+                        }
+                        v = vv*vv;
+                        v = (1-v)*(1-v)/(v+lambdacur);
+                        f = buf.tsxy[i,nx+1];
+                        wf1 = wf1+v*f;
+                        ws1 = ws1+v;
+                        vv = vv*invrdecay;
+                        if( vv>=1.0 )
+                        {
+                            continue;
+                        }
+                        for(layeridx=2; layeridx<=s.nlayers-1; layeridx++)
+                        {
+                            if( layeridx==s.nlayers-1 )
+                            {
+                                lambdacur = s.lambdalast;
+                            }
+                            v = vv*vv;
+                            v = (1-v)*(1-v)/(v+lambdacur);
+                            f = buf.tsxy[i,nx+layeridx];
+                            buf.tsyw[layeridx] = buf.tsyw[layeridx]+v*f;
+                            buf.tsw[layeridx] = buf.tsw[layeridx]+v;
+                            vv = vv*invrdecay;
+                            if( vv>=1.0 )
+                            {
+                                break;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // General case
+                        //
+                        for(layeridx=0; layeridx<=s.nlayers-1; layeridx++)
+                        {
+                            if( layeridx==s.nlayers-1 )
+                            {
+                                lambdacur = s.lambdalast;
+                            }
+                            if( vv>=1.0 )
+                            {
+                                break;
+                            }
+                            v = vv*vv;
+                            v = (1-v)*(1-v)/(v+lambdacur);
+                            for(j=0; j<=ny-1; j++)
+                            {
+                                f = buf.tsxy[i,nx+layeridx*ny+j];
+                                buf.tsyw[layeridx*ny+j] = buf.tsyw[layeridx*ny+j]+v*f;
+                            }
+                            buf.tsw[layeridx] = buf.tsw[layeridx]+v;
+                            lambdacur = lambdacur*lambdadecay;
+                            vv = vv*invrdecay;
+                        }
+                    }
+                }
+                if( fastcalcpossible )
+                {
+                    
+                    //
+                    // Important special case, finalize evaluations
+                    //
+                    buf.tsyw[0] = wf0;
+                    buf.tsw[0] = ws0;
+                    buf.tsyw[1] = wf1;
+                    buf.tsw[1] = ws1;
+                }
+                for(j=0; j<=ny-1; j++)
+                {
+                    y[j] = s.globalprior[j];
+                }
+                for(layeridx=0; layeridx<=s.nlayers-1; layeridx++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        y[j] = y[j]+buf.tsyw[layeridx*ny+j]/buf.tsw[layeridx];
+                    }
+                }
+                return;
+            }
+            
+            //
+            //
+            //
+            alglib.ap.assert(false, "IDWTsCalcBuf: unexpected AlgoType");
+        }
+
+
+        /*************************************************************************
+        This function fits IDW model to the dataset using current IDW construction
+        algorithm. A model being built and fitting report are returned.
+
+        INPUT PARAMETERS:
+            State   -   builder object
+
+        OUTPUT PARAMETERS:
+            Model   -   an IDW model built with current algorithm
+            Rep     -   model fitting report, fields of this structure contain
+                        information about average fitting errors.
+                        
+        NOTE: although IDW-MSTAB algorithm is an  interpolation  method,  i.e.  it
+              tries to fit the model exactly, it can  handle  datasets  with  non-
+              distinct points which can not be fit exactly; in such  cases  least-
+              squares fitting is performed.
+           
+          -- ALGLIB --
+             Copyright 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwfit(idwbuilder state,
+            idwmodel model,
+            idwreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int i0 = 0;
+            int j = 0;
+            int k = 0;
+            int layeridx = 0;
+            int srcidx = 0;
+            double v = 0;
+            double vv = 0;
+            int npoints = 0;
+            int nx = 0;
+            int ny = 0;
+            double rcur = 0;
+            double lambdacur = 0;
+            double rss = 0;
+            double tss = 0;
+
+            nx = state.nx;
+            ny = state.ny;
+            npoints = state.npoints;
+            
+            //
+            // Clear report fields
+            //
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.maxerror = 0;
+            rep.r2 = 1.0;
+            
+            //
+            // Quick exit for empty dataset
+            //
+            if( state.npoints==0 )
+            {
+                model.nx = nx;
+                model.ny = ny;
+                model.globalprior = new double[ny];
+                for(i=0; i<=ny-1; i++)
+                {
+                    model.globalprior[i] = 0;
+                }
+                model.algotype = 0;
+                model.nlayers = 0;
+                model.r0 = 1;
+                model.rdecay = 0.5;
+                model.lambda0 = 0;
+                model.lambdalast = 0;
+                model.lambdadecay = 1;
+                model.shepardp = 2;
+                model.npoints = 0;
+                idwcreatecalcbuffer(model, model.buffer, _params);
+                return;
+            }
+            
+            //
+            // Compute temporaries which will be required later:
+            // * global mean
+            //
+            alglib.ap.assert(state.npoints>0, "IDWFit: integrity check failed");
+            apserv.rvectorsetlengthatleast(ref state.tmpmean, ny, _params);
+            for(j=0; j<=ny-1; j++)
+            {
+                state.tmpmean[j] = 0;
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=ny-1; j++)
+                {
+                    state.tmpmean[j] = state.tmpmean[j]+state.xy[i*(nx+ny)+nx+j];
+                }
+            }
+            for(j=0; j<=ny-1; j++)
+            {
+                state.tmpmean[j] = state.tmpmean[j]/npoints;
+            }
+            
+            //
+            // Compute global prior
+            //
+            // NOTE: for original Shepard's method it is always mean value
+            //
+            apserv.rvectorsetlengthatleast(ref model.globalprior, ny, _params);
+            for(j=0; j<=ny-1; j++)
+            {
+                model.globalprior[j] = state.tmpmean[j];
+            }
+            if( state.algotype!=0 )
+            {
+                
+                //
+                // Algorithm is set to one of the "advanced" versions with search
+                // radius which can handle non-mean prior term
+                //
+                if( state.priortermtype==0 )
+                {
+                    
+                    //
+                    // User-specified prior
+                    //
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        model.globalprior[j] = state.priortermval[j];
+                    }
+                }
+                if( state.priortermtype==3 )
+                {
+                    
+                    //
+                    // Zero prior
+                    //
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        model.globalprior[j] = 0;
+                    }
+                }
+            }
+            
+            //
+            // Textbook Shepard
+            //
+            if( state.algotype==0 )
+            {
+                
+                //
+                // Initialize model
+                //
+                model.algotype = 0;
+                model.nx = nx;
+                model.ny = ny;
+                model.nlayers = 1;
+                model.r0 = 1;
+                model.rdecay = 0.5;
+                model.lambda0 = 0;
+                model.lambdalast = 0;
+                model.lambdadecay = 1;
+                model.shepardp = state.shepardp;
+                
+                //
+                // Copy dataset
+                //
+                apserv.rvectorsetlengthatleast(ref model.shepardxy, npoints*(nx+ny), _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        model.shepardxy[i*(nx+ny)+j] = state.xy[i*(nx+ny)+j];
+                    }
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        model.shepardxy[i*(nx+ny)+nx+j] = state.xy[i*(nx+ny)+nx+j]-model.globalprior[j];
+                    }
+                }
+                model.npoints = npoints;
+                
+                //
+                // Prepare internal buffer
+                // Evaluate report fields
+                //
+                idwcreatecalcbuffer(model, model.buffer, _params);
+                errormetricsviacalc(state, model, rep, _params);
+                return;
+            }
+            
+            //
+            // Textbook modified Shepard's method
+            //
+            if( state.algotype==1 )
+            {
+                
+                //
+                // Initialize model
+                //
+                model.algotype = 1;
+                model.nx = nx;
+                model.ny = ny;
+                model.nlayers = 1;
+                model.r0 = state.r0;
+                model.rdecay = 1;
+                model.lambda0 = 0;
+                model.lambdalast = 0;
+                model.lambdadecay = 1;
+                model.shepardp = 0;
+                
+                //
+                // Build kd-tree search structure
+                //
+                apserv.rmatrixsetlengthatleast(ref state.tmpxy, npoints, nx+ny, _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        state.tmpxy[i,j] = state.xy[i*(nx+ny)+j];
+                    }
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        state.tmpxy[i,nx+j] = state.xy[i*(nx+ny)+nx+j]-model.globalprior[j];
+                    }
+                }
+                nearestneighbor.kdtreebuild(state.tmpxy, npoints, nx, ny, 2, model.tree, _params);
+                
+                //
+                // Prepare internal buffer
+                // Evaluate report fields
+                //
+                idwcreatecalcbuffer(model, model.buffer, _params);
+                errormetricsviacalc(state, model, rep, _params);
+                return;
+            }
+            
+            //
+            // MSTAB algorithm
+            //
+            if( state.algotype==2 )
+            {
+                alglib.ap.assert(state.nlayers>=1, "IDWFit: integrity check failed");
+                
+                //
+                // Initialize model
+                //
+                model.algotype = 2;
+                model.nx = nx;
+                model.ny = ny;
+                model.nlayers = state.nlayers;
+                model.r0 = state.r0;
+                model.rdecay = 0.5;
+                model.lambda0 = state.lambda0;
+                model.lambdadecay = 1.0;
+                model.lambdalast = meps;
+                model.shepardp = 0;
+                
+                //
+                // Build kd-tree search structure,
+                // prepare input residuals for the first layer of the model
+                //
+                apserv.rmatrixsetlengthatleast(ref state.tmpxy, npoints, nx, _params);
+                apserv.rmatrixsetlengthatleast(ref state.tmplayers, npoints, nx+ny*(state.nlayers+1), _params);
+                apserv.ivectorsetlengthatleast(ref state.tmptags, npoints, _params);
+                for(i=0; i<=npoints-1; i++)
+                {
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        v = state.xy[i*(nx+ny)+j];
+                        state.tmpxy[i,j] = v;
+                        state.tmplayers[i,j] = v;
+                    }
+                    state.tmptags[i] = i;
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        state.tmplayers[i,nx+j] = state.xy[i*(nx+ny)+nx+j]-model.globalprior[j];
+                    }
+                }
+                nearestneighbor.kdtreebuildtagged(state.tmpxy, state.tmptags, npoints, nx, 0, 2, state.tmptree, _params);
+                
+                //
+                // Iteratively build layer by layer
+                //
+                apserv.rvectorsetlengthatleast(ref state.tmpx, nx, _params);
+                apserv.rvectorsetlengthatleast(ref state.tmpwy, ny, _params);
+                apserv.rvectorsetlengthatleast(ref state.tmpw, ny, _params);
+                for(layeridx=0; layeridx<=state.nlayers-1; layeridx++)
+                {
+                    
+                    //
+                    // Determine layer metrics
+                    //
+                    rcur = model.r0*Math.Pow(model.rdecay, layeridx);
+                    lambdacur = model.lambda0*Math.Pow(model.lambdadecay, layeridx);
+                    if( layeridx==state.nlayers-1 )
+                    {
+                        lambdacur = model.lambdalast;
+                    }
+                    
+                    //
+                    // For each point compute residual from fitting with current layer
+                    //
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        for(j=0; j<=nx-1; j++)
+                        {
+                            state.tmpx[j] = state.tmplayers[i,j];
+                        }
+                        k = nearestneighbor.kdtreequeryrnn(state.tmptree, state.tmpx, rcur, true, _params);
+                        nearestneighbor.kdtreequeryresultstags(state.tmptree, ref state.tmptags, _params);
+                        nearestneighbor.kdtreequeryresultsdistances(state.tmptree, ref state.tmpdist, _params);
+                        for(j=0; j<=ny-1; j++)
+                        {
+                            state.tmpwy[j] = 0;
+                            state.tmpw[j] = w0;
+                        }
+                        for(i0=0; i0<=k-1; i0++)
+                        {
+                            vv = state.tmpdist[i0]/rcur;
+                            vv = vv*vv;
+                            v = (1-vv)*(1-vv)/(vv+lambdacur);
+                            srcidx = state.tmptags[i0];
+                            for(j=0; j<=ny-1; j++)
+                            {
+                                state.tmpwy[j] = state.tmpwy[j]+v*state.tmplayers[srcidx,nx+layeridx*ny+j];
+                                state.tmpw[j] = state.tmpw[j]+v;
+                            }
+                        }
+                        for(j=0; j<=ny-1; j++)
+                        {
+                            v = state.tmplayers[i,nx+layeridx*ny+j];
+                            state.tmplayers[i,nx+(layeridx+1)*ny+j] = v-state.tmpwy[j]/state.tmpw[j];
+                        }
+                    }
+                }
+                nearestneighbor.kdtreebuild(state.tmplayers, npoints, nx, ny*state.nlayers, 2, model.tree, _params);
+                
+                //
+                // Evaluate report fields
+                //
+                rep.rmserror = 0;
+                rep.avgerror = 0;
+                rep.maxerror = 0;
+                rss = 0;
+                tss = 0;
+                for(i=0; i<=npoints-1; i++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        v = Math.Abs(state.tmplayers[i,nx+state.nlayers*ny+j]);
+                        rep.rmserror = rep.rmserror+v*v;
+                        rep.avgerror = rep.avgerror+v;
+                        rep.maxerror = Math.Max(rep.maxerror, Math.Abs(v));
+                        rss = rss+v*v;
+                        tss = tss+math.sqr(state.xy[i*(nx+ny)+nx+j]-state.tmpmean[j]);
+                    }
+                }
+                rep.rmserror = Math.Sqrt(rep.rmserror/(npoints*ny));
+                rep.avgerror = rep.avgerror/(npoints*ny);
+                rep.r2 = 1.0-rss/apserv.coalesce(tss, 1.0, _params);
+                
+                //
+                // Prepare internal buffer
+                //
+                idwcreatecalcbuffer(model, model.buffer, _params);
+                return;
+            }
+            
+            //
+            // Unknown algorithm
+            //
+            alglib.ap.assert(false, "IDWFit: integrity check failed, unexpected algorithm");
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 28.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwalloc(alglib.serializer s,
+            idwmodel model,
+            alglib.xparams _params)
+        {
+            bool processed = new bool();
+
+            
+            //
+            // Header
+            //
+            s.alloc_entry();
+            
+            //
+            // Algorithm type and fields which are set for all algorithms
+            //
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            apserv.allocrealarray(s, model.globalprior, -1, _params);
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            
+            //
+            // Algorithm-specific fields
+            //
+            processed = false;
+            if( model.algotype==0 )
+            {
+                s.alloc_entry();
+                apserv.allocrealarray(s, model.shepardxy, -1, _params);
+                processed = true;
+            }
+            if( model.algotype>0 )
+            {
+                nearestneighbor.kdtreealloc(s, model.tree, _params);
+                processed = true;
+            }
+            alglib.ap.assert(processed, "IDW: integrity check failed during serialization");
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 28.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwserialize(alglib.serializer s,
+            idwmodel model,
+            alglib.xparams _params)
+        {
+            bool processed = new bool();
+
+            
+            //
+            // Header
+            //
+            s.serialize_int(scodes.getidwserializationcode(_params));
+            
+            //
+            // Algorithm type and fields which are set for all algorithms
+            //
+            s.serialize_int(model.algotype);
+            s.serialize_int(model.nx);
+            s.serialize_int(model.ny);
+            apserv.serializerealarray(s, model.globalprior, -1, _params);
+            s.serialize_int(model.nlayers);
+            s.serialize_double(model.r0);
+            s.serialize_double(model.rdecay);
+            s.serialize_double(model.lambda0);
+            s.serialize_double(model.lambdalast);
+            s.serialize_double(model.lambdadecay);
+            s.serialize_double(model.shepardp);
+            
+            //
+            // Algorithm-specific fields
+            //
+            processed = false;
+            if( model.algotype==0 )
+            {
+                s.serialize_int(model.npoints);
+                apserv.serializerealarray(s, model.shepardxy, -1, _params);
+                processed = true;
+            }
+            if( model.algotype>0 )
+            {
+                nearestneighbor.kdtreeserialize(s, model.tree, _params);
+                processed = true;
+            }
+            alglib.ap.assert(processed, "IDW: integrity check failed during serialization");
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 28.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void idwunserialize(alglib.serializer s,
+            idwmodel model,
+            alglib.xparams _params)
+        {
+            bool processed = new bool();
+            int scode = 0;
+
+            
+            //
+            // Header
+            //
+            scode = s.unserialize_int();
+            alglib.ap.assert(scode==scodes.getidwserializationcode(_params), "IDWUnserialize: stream header corrupted");
+            
+            //
+            // Algorithm type and fields which are set for all algorithms
+            //
+            model.algotype = s.unserialize_int();
+            model.nx = s.unserialize_int();
+            model.ny = s.unserialize_int();
+            apserv.unserializerealarray(s, ref model.globalprior, _params);
+            model.nlayers = s.unserialize_int();
+            model.r0 = s.unserialize_double();
+            model.rdecay = s.unserialize_double();
+            model.lambda0 = s.unserialize_double();
+            model.lambdalast = s.unserialize_double();
+            model.lambdadecay = s.unserialize_double();
+            model.shepardp = s.unserialize_double();
+            
+            //
+            // Algorithm-specific fields
+            //
+            processed = false;
+            if( model.algotype==0 )
+            {
+                model.npoints = s.unserialize_int();
+                apserv.unserializerealarray(s, ref model.shepardxy, _params);
+                processed = true;
+            }
+            if( model.algotype>0 )
+            {
+                nearestneighbor.kdtreeunserialize(s, model.tree, _params);
+                processed = true;
+            }
+            alglib.ap.assert(processed, "IDW: integrity check failed during serialization");
+            
+            //
+            // Temporary buffers
+            //
+            idwcreatecalcbuffer(model, model.buffer, _params);
+        }
+
+
+        /*************************************************************************
+        This function evaluates error metrics for the model  using  IDWTsCalcBuf()
+        to calculate model at each point.
+
+        NOTE: modern IDW algorithms (MSTAB, MSMOOTH) can generate residuals during
+              model construction, so they do not need this function  in  order  to
+              evaluate error metrics.
+
+        Following fields of Rep are filled:
+        * rep.rmserror
+        * rep.avgerror
+        * rep.maxerror
+        * rep.r2
+           
+          -- ALGLIB --
+             Copyright 22.10.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void errormetricsviacalc(idwbuilder state,
+            idwmodel model,
+            idwreport rep,
+            alglib.xparams _params)
+        {
+            int npoints = 0;
+            int nx = 0;
+            int ny = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double vv = 0;
+            double rss = 0;
+            double tss = 0;
+
+            npoints = state.npoints;
+            nx = state.nx;
+            ny = state.ny;
+            if( npoints==0 )
+            {
+                rep.rmserror = 0;
+                rep.avgerror = 0;
+                rep.maxerror = 0;
+                rep.r2 = 1;
+                return;
+            }
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.maxerror = 0;
+            rss = 0;
+            tss = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=nx-1; j++)
+                {
+                    model.buffer.x[j] = state.xy[i*(nx+ny)+j];
+                }
+                idwtscalcbuf(model, model.buffer, model.buffer.x, ref model.buffer.y, _params);
+                for(j=0; j<=ny-1; j++)
+                {
+                    vv = state.xy[i*(nx+ny)+nx+j];
+                    v = Math.Abs(vv-model.buffer.y[j]);
+                    rep.rmserror = rep.rmserror+v*v;
+                    rep.avgerror = rep.avgerror+v;
+                    rep.maxerror = Math.Max(rep.maxerror, v);
+                    rss = rss+v*v;
+                    tss = tss+math.sqr(vv-state.tmpmean[j]);
+                }
+            }
+            rep.rmserror = Math.Sqrt(rep.rmserror/(npoints*ny));
+            rep.avgerror = rep.avgerror/(npoints*ny);
+            rep.r2 = 1.0-rss/apserv.coalesce(tss, 1.0, _params);
+        }
+
+
+    }
+    public class ratint
+    {
+        /*************************************************************************
+        Barycentric interpolant.
+        *************************************************************************/
+        public class barycentricinterpolant : apobject
+        {
+            public int n;
+            public double sy;
+            public double[] x;
+            public double[] y;
+            public double[] w;
+            public barycentricinterpolant()
+            {
+                init();
+            }
+            public override void init()
+            {
+                x = new double[0];
+                y = new double[0];
+                w = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                barycentricinterpolant _result = new barycentricinterpolant();
+                _result.n = n;
+                _result.sy = sy;
+                _result.x = (double[])x.Clone();
+                _result.y = (double[])y.Clone();
+                _result.w = (double[])w.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+            
+            //
+            // 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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                    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 fitsphere
+    {
+        public class fitsphereinternalreport : apobject
+        {
+            public int nfev;
+            public int iterationscount;
+            public fitsphereinternalreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                fitsphereinternalreport _result = new fitsphereinternalreport();
+                _result.nfev = nfev;
+                _result.iterationscount = iterationscount;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        Fits least squares (LS) circle (or NX-dimensional sphere) to data  (a  set
+        of points in NX-dimensional space).
+
+        Least squares circle minimizes sum of squared deviations between distances
+        from points to the center and  some  "candidate"  radius,  which  is  also
+        fitted to the data.
+
+        INPUT PARAMETERS:
+            XY      -   array[NPoints,NX] (or larger), contains dataset.
+                        One row = one point in NX-dimensional space.
+            NPoints -   dataset size, NPoints>0
+            NX      -   space dimensionality, NX>0 (1, 2, 3, 4, 5 and so on)
+
+        OUTPUT PARAMETERS:
+            CX      -   central point for a sphere
+            R       -   radius
+                                            
+          -- ALGLIB --
+             Copyright 07.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fitspherels(double[,] xy,
+            int npoints,
+            int nx,
+            ref double[] cx,
+            ref double r,
+            alglib.xparams _params)
+        {
+            double dummy = 0;
+
+            cx = new double[0];
+            r = 0;
+
+            fitspherex(xy, npoints, nx, 0, 0.0, 0, 0.0, ref cx, ref dummy, ref r, _params);
+        }
+
+
+        /*************************************************************************
+        Fits minimum circumscribed (MC) circle (or NX-dimensional sphere) to  data
+        (a set of points in NX-dimensional space).
+
+        INPUT PARAMETERS:
+            XY      -   array[NPoints,NX] (or larger), contains dataset.
+                        One row = one point in NX-dimensional space.
+            NPoints -   dataset size, NPoints>0
+            NX      -   space dimensionality, NX>0 (1, 2, 3, 4, 5 and so on)
+
+        OUTPUT PARAMETERS:
+            CX      -   central point for a sphere
+            RHi     -   radius
+
+        NOTE: this function is an easy-to-use wrapper around more powerful "expert"
+              function fitspherex().
+              
+              This  wrapper  is optimized  for  ease of use and stability - at the
+              cost of somewhat lower  performance  (we  have  to  use  very  tight
+              stopping criteria for inner optimizer because we want to  make  sure
+              that it will converge on any dataset).
+              
+              If you are ready to experiment with settings of  "expert"  function,
+              you can achieve ~2-4x speedup over standard "bulletproof" settings.
+
+                                            
+          -- ALGLIB --
+             Copyright 14.04.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fitspheremc(double[,] xy,
+            int npoints,
+            int nx,
+            ref double[] cx,
+            ref double rhi,
+            alglib.xparams _params)
+        {
+            double dummy = 0;
+
+            cx = new double[0];
+            rhi = 0;
+
+            fitspherex(xy, npoints, nx, 1, 0.0, 0, 0.0, ref cx, ref dummy, ref rhi, _params);
+        }
+
+
+        /*************************************************************************
+        Fits maximum inscribed circle (or NX-dimensional sphere) to data (a set of
+        points in NX-dimensional space).
+
+        INPUT PARAMETERS:
+            XY      -   array[NPoints,NX] (or larger), contains dataset.
+                        One row = one point in NX-dimensional space.
+            NPoints -   dataset size, NPoints>0
+            NX      -   space dimensionality, NX>0 (1, 2, 3, 4, 5 and so on)
+
+        OUTPUT PARAMETERS:
+            CX      -   central point for a sphere
+            RLo     -   radius
+
+        NOTE: this function is an easy-to-use wrapper around more powerful "expert"
+              function fitspherex().
+              
+              This  wrapper  is optimized  for  ease of use and stability - at the
+              cost of somewhat lower  performance  (we  have  to  use  very  tight
+              stopping criteria for inner optimizer because we want to  make  sure
+              that it will converge on any dataset).
+              
+              If you are ready to experiment with settings of  "expert"  function,
+              you can achieve ~2-4x speedup over standard "bulletproof" settings.
+
+                                            
+          -- ALGLIB --
+             Copyright 14.04.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fitspheremi(double[,] xy,
+            int npoints,
+            int nx,
+            ref double[] cx,
+            ref double rlo,
+            alglib.xparams _params)
+        {
+            double dummy = 0;
+
+            cx = new double[0];
+            rlo = 0;
+
+            fitspherex(xy, npoints, nx, 2, 0.0, 0, 0.0, ref cx, ref rlo, ref dummy, _params);
+        }
+
+
+        /*************************************************************************
+        Fits minimum zone circle (or NX-dimensional sphere)  to  data  (a  set  of
+        points in NX-dimensional space).
+
+        INPUT PARAMETERS:
+            XY      -   array[NPoints,NX] (or larger), contains dataset.
+                        One row = one point in NX-dimensional space.
+            NPoints -   dataset size, NPoints>0
+            NX      -   space dimensionality, NX>0 (1, 2, 3, 4, 5 and so on)
+
+        OUTPUT PARAMETERS:
+            CX      -   central point for a sphere
+            RLo     -   radius of inscribed circle
+            RHo     -   radius of circumscribed circle
+
+        NOTE: this function is an easy-to-use wrapper around more powerful "expert"
+              function fitspherex().
+              
+              This  wrapper  is optimized  for  ease of use and stability - at the
+              cost of somewhat lower  performance  (we  have  to  use  very  tight
+              stopping criteria for inner optimizer because we want to  make  sure
+              that it will converge on any dataset).
+              
+              If you are ready to experiment with settings of  "expert"  function,
+              you can achieve ~2-4x speedup over standard "bulletproof" settings.
+
+                                            
+          -- ALGLIB --
+             Copyright 14.04.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fitspheremz(double[,] xy,
+            int npoints,
+            int nx,
+            ref double[] cx,
+            ref double rlo,
+            ref double rhi,
+            alglib.xparams _params)
+        {
+            cx = new double[0];
+            rlo = 0;
+            rhi = 0;
+
+            fitspherex(xy, npoints, nx, 3, 0.0, 0, 0.0, ref cx, ref rlo, ref rhi, _params);
+        }
+
+
+        /*************************************************************************
+        Fitting minimum circumscribed, maximum inscribed or minimum  zone  circles
+        (or NX-dimensional spheres)  to  data  (a  set of points in NX-dimensional
+        space).
+
+        This  is  expert  function  which  allows  to  tweak  many  parameters  of
+        underlying nonlinear solver:
+        * stopping criteria for inner iterations
+        * number of outer iterations
+        * penalty coefficient used to handle  nonlinear  constraints  (we  convert
+          unconstrained nonsmooth optimization problem ivolving max() and/or min()
+          operations to quadratically constrained smooth one).
+
+        You may tweak all these parameters or only some  of  them,  leaving  other
+        ones at their default state - just specify zero  value,  and  solver  will
+        fill it with appropriate default one.
+
+        These comments also include some discussion of  approach  used  to  handle
+        such unusual fitting problem,  its  stability,  drawbacks  of  alternative
+        methods, and convergence properties.
+          
+        INPUT PARAMETERS:
+            XY      -   array[NPoints,NX] (or larger), contains dataset.
+                        One row = one point in NX-dimensional space.
+            NPoints -   dataset size, NPoints>0
+            NX      -   space dimensionality, NX>0 (1, 2, 3, 4, 5 and so on)
+            ProblemType-used to encode problem type:
+                        * 0 for least squares circle
+                        * 1 for minimum circumscribed circle/sphere fitting (MC)
+                        * 2 for  maximum inscribed circle/sphere fitting (MI)
+                        * 3 for minimum zone circle fitting (difference between
+                            Rhi and Rlo is minimized), denoted as MZ
+            EpsX    -   stopping condition for NLC optimizer:
+                        * must be non-negative
+                        * use 0 to choose default value (1.0E-12 is used by default)
+                        * you may specify larger values, up to 1.0E-6, if you want
+                          to   speed-up   solver;   NLC   solver  performs several
+                          preconditioned  outer  iterations,   so   final   result
+                          typically has precision much better than EpsX.
+            AULIts  -   number of outer iterations performed by NLC optimizer:
+                        * must be non-negative
+                        * use 0 to choose default value (20 is used by default)
+                        * you may specify values smaller than 20 if you want to
+                          speed up solver; 10 often results in good combination of
+                          precision and speed; sometimes you may get good results
+                          with just 6 outer iterations.
+                        Ignored for ProblemType=0.
+            Penalty -   penalty coefficient for NLC optimizer:
+                        * must be non-negative
+                        * use 0 to choose default value (1.0E6 in current version)
+                        * it should be really large, 1.0E6...1.0E7 is a good value
+                          to start from;
+                        * generally, default value is good enough
+                        Ignored for ProblemType=0.
+
+        OUTPUT PARAMETERS:
+            CX      -   central point for a sphere
+            RLo     -   radius:
+                        * for ProblemType=2,3, radius of the inscribed sphere
+                        * for ProblemType=0 - radius of the least squares sphere
+                        * for ProblemType=1 - zero
+            RHo     -   radius:
+                        * for ProblemType=1,3, radius of the circumscribed sphere
+                        * for ProblemType=0 - radius of the least squares sphere
+                        * for ProblemType=2 - zero
+
+        NOTE: ON THE UNIQUENESS OF SOLUTIONS
+
+        ALGLIB provides solution to several related circle fitting  problems:   MC
+        (minimum circumscribed), MI (maximum inscribed)   and   MZ  (minimum zone)
+        fitting, LS (least squares) fitting.
+
+        It  is  important  to  note  that  among these problems only MC and LS are
+        convex and have unique solution independently from starting point.
+
+        As  for MI,  it  may (or  may  not, depending on dataset properties)  have
+        multiple solutions, and it always  has  one degenerate solution C=infinity
+        which corresponds to infinitely large radius. Thus, there are no guarantees
+        that solution to  MI returned by this solver will be the best one (and  no
+        one can provide you with such guarantee because problem is  NP-hard).  The
+        only guarantee you have is that this solution is locally optimal, i.e.  it
+        can not be improved by infinitesimally small tweaks in the parameters.
+
+        It  is  also  possible  to "run away" to infinity when  started  from  bad
+        initial point located outside of point cloud (or when point cloud does not
+        span entire circumference/surface of the sphere).
+
+        Finally,  MZ (minimum zone circle) stands somewhere between MC  and  MI in
+        stability. It is somewhat regularized by "circumscribed" term of the merit
+        function; however, solutions to  MZ may be non-unique, and in some unlucky
+        cases it is also possible to "run away to infinity".
+
+
+        NOTE: ON THE NONLINEARLY CONSTRAINED PROGRAMMING APPROACH
+
+        The problem formulation for MC  (minimum circumscribed   circle;  for  the
+        sake of simplicity we omit MZ and MI here) is:
+
+                [     [         ]2 ]
+            min [ max [ XY[i]-C ]  ]
+             C  [  i  [         ]  ]
+
+        i.e. it is unconstrained nonsmooth optimization problem of finding  "best"
+        central point, with radius R being unambiguously  determined  from  C.  In
+        order to move away from non-smoothness we use following reformulation:
+
+                [   ]                  [         ]2
+            min [ R ] subject to R>=0, [ XY[i]-C ]  <= R^2
+            C,R [   ]                  [         ]
+            
+        i.e. it becomes smooth quadratically constrained optimization problem with
+        linear target function. Such problem statement is 100% equivalent  to  the
+        original nonsmooth one, but much easier  to  approach.  We solve  it  with
+        MinNLC solver provided by ALGLIB.
+
+
+        NOTE: ON INSTABILITY OF SEQUENTIAL LINEARIZATION APPROACH
+
+        ALGLIB  has  nonlinearly  constrained  solver which proved to be stable on
+        such problems. However, some authors proposed to linearize constraints  in
+        the vicinity of current approximation (Ci,Ri) and to get next  approximate
+        solution (Ci+1,Ri+1) as solution to linear programming problem. Obviously,
+        LP problems are easier than nonlinearly constrained ones.
+
+        Indeed,  such approach  to   MC/MI/MZ   resulted   in  ~10-20x increase in
+        performance (when compared with NLC solver). However, it turned  out  that
+        in some cases linearized model fails to predict correct direction for next
+        step and tells us that we converged to solution even when we are still 2-4
+        digits of precision away from it.
+
+        It is important that it is not failure of LP solver - it is failure of the
+        linear model;  even  when  solved  exactly,  it  fails  to  handle  subtle
+        nonlinearities which arise near the solution. We validated it by comparing
+        results returned by ALGLIB linear solver with that of MATLAB.
+
+        In our experiments with linearization:
+        * MC failed most often, at both realistic and synthetic datasets
+        * MI sometimes failed, but sometimes succeeded
+        * MZ often  succeeded; our guess is that presence of two independent  sets
+          of constraints (one set for Rlo and another one for Rhi) and  two  terms
+          in the target function (Rlo and Rhi) regularizes task,  so  when  linear
+          model fails to handle nonlinearities from Rlo, it uses  Rhi  as  a  hint
+          (and vice versa).
+          
+        Because linearization approach failed to achieve stable results, we do not
+        include it in ALGLIB.
+
+                                            
+          -- ALGLIB --
+             Copyright 14.04.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fitspherex(double[,] xy,
+            int npoints,
+            int nx,
+            int problemtype,
+            double epsx,
+            int aulits,
+            double penalty,
+            ref double[] cx,
+            ref double rlo,
+            ref double rhi,
+            alglib.xparams _params)
+        {
+            fitsphereinternalreport rep = new fitsphereinternalreport();
+
+            cx = new double[0];
+            rlo = 0;
+            rhi = 0;
+
+            alglib.ap.assert(math.isfinite(penalty) && (double)(penalty)>=(double)(0), "FitSphereX: Penalty<0 or is not finite");
+            alglib.ap.assert(math.isfinite(epsx) && (double)(epsx)>=(double)(0), "FitSphereX: EpsX<0 or is not finite");
+            alglib.ap.assert(aulits>=0, "FitSphereX: AULIts<0");
+            fitsphereinternal(xy, npoints, nx, problemtype, 0, epsx, aulits, penalty, ref cx, ref rlo, ref rhi, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Fitting minimum circumscribed, maximum inscribed or minimum  zone  circles
+        (or NX-dimensional spheres)  to  data  (a  set of points in NX-dimensional
+        space).
+
+        Internal computational function.
+
+        INPUT PARAMETERS:
+            XY      -   array[NPoints,NX] (or larger), contains dataset.
+                        One row = one point in NX-dimensional space.
+            NPoints -   dataset size, NPoints>0
+            NX      -   space dimensionality, NX>0 (1, 2, 3, 4, 5 and so on)
+            ProblemType-used to encode problem type:
+                        * 0 for least squares circle
+                        * 1 for minimum circumscribed circle/sphere fitting (MC)
+                        * 2 for  maximum inscribed circle/sphere fitting (MI)
+                        * 3 for minimum zone circle fitting (difference between
+                            Rhi and Rlo is minimized), denoted as MZ
+            SolverType- solver to use:
+                        * 0 use best solver available (1 in current version)
+                        * 1 use nonlinearly constrained optimization approach, AUL
+                            (it is roughly 10-20 times  slower  than  SPC-LIN, but
+                            much more stable)
+                        * 2 use special fast IMPRECISE solver, SPC-LIN  sequential
+                            linearization approach; SPC-LIN is fast, but sometimes
+                            fails to converge with more than 3 digits of precision;
+                            see comments below.
+                            NOT RECOMMENDED UNLESS YOU REALLY NEED HIGH PERFORMANCE
+                            AT THE COST OF SOME PRECISION.
+                        * 3 use nonlinearly constrained optimization approach, SLP
+                            (most robust one, but somewhat slower than AUL)
+                        Ignored for ProblemType=0.
+            EpsX    -   stopping criteria for SLP and NLC optimizers:
+                        * must be non-negative
+                        * use 0 to choose default value (1.0E-12 is used by default)
+                        * if you use SLP solver, you should use default values
+                        * if you use NLC solver, you may specify larger values, up
+                          to 1.0E-6, if you want to speed-up  solver;  NLC  solver
+                          performs several preconditioned outer iterations, so final
+                          result typically has precision much better than EpsX.
+            AULIts  -   number of iterations performed by NLC optimizer:
+                        * must be non-negative
+                        * use 0 to choose default value (20 is used by default)
+                        * you may specify values smaller than 20 if you want to
+                          speed up solver; 10 often results in good combination of
+                          precision and speed
+                        Ignored for ProblemType=0.
+            Penalty -   penalty coefficient for NLC optimizer (ignored  for  SLP):
+                        * must be non-negative
+                        * use 0 to choose default value (1.0E6 in current version)
+                        * it should be really large, 1.0E6...1.0E7 is a good value
+                          to start from;
+                        * generally, default value is good enough
+                        * ignored by SLP optimizer
+                        Ignored for ProblemType=0.
+
+        OUTPUT PARAMETERS:
+            CX      -   central point for a sphere
+            RLo     -   radius:
+                        * for ProblemType=2,3, radius of the inscribed sphere
+                        * for ProblemType=0 - radius of the least squares sphere
+                        * for ProblemType=1 - zero
+            RHo     -   radius:
+                        * for ProblemType=1,3, radius of the circumscribed sphere
+                        * for ProblemType=0 - radius of the least squares sphere
+                        * for ProblemType=2 - zero
+                                            
+          -- ALGLIB --
+             Copyright 14.04.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void fitsphereinternal(double[,] xy,
+            int npoints,
+            int nx,
+            int problemtype,
+            int solvertype,
+            double epsx,
+            int aulits,
+            double penalty,
+            ref double[] cx,
+            ref double rlo,
+            ref double rhi,
+            fitsphereinternalreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double vv = 0;
+            int cpr = 0;
+            bool userlo = new bool();
+            bool userhi = new bool();
+            double vlo = 0;
+            double vhi = 0;
+            double[] vmin = new double[0];
+            double[] vmax = new double[0];
+            double spread = 0;
+            double[] pcr = new double[0];
+            double[] scr = new double[0];
+            double[] bl = new double[0];
+            double[] bu = new double[0];
+            int suboffset = 0;
+            int dstrow = 0;
+            minnlc.minnlcstate nlcstate = new minnlc.minnlcstate();
+            minnlc.minnlcreport nlcrep = new minnlc.minnlcreport();
+            double[,] cmatrix = new double[0,0];
+            int[] ct = new int[0];
+            int outeridx = 0;
+            int maxouterits = 0;
+            int maxits = 0;
+            double safeguard = 0;
+            double bi = 0;
+            minbleic.minbleicstate blcstate = new minbleic.minbleicstate();
+            minbleic.minbleicreport blcrep = new minbleic.minbleicreport();
+            double[] prevc = new double[0];
+            minlm.minlmstate lmstate = new minlm.minlmstate();
+            minlm.minlmreport lmrep = new minlm.minlmreport();
+
+            cx = new double[0];
+            rlo = 0;
+            rhi = 0;
+
+            
+            //
+            // Check input parameters
+            //
+            alglib.ap.assert(npoints>0, "FitSphereX: NPoints<=0");
+            alglib.ap.assert(nx>0, "FitSphereX: NX<=0");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, npoints, nx, _params), "FitSphereX: XY contains infinite or NAN values");
+            alglib.ap.assert(problemtype>=0 && problemtype<=3, "FitSphereX: ProblemType is neither 0, 1, 2 or 3");
+            alglib.ap.assert(solvertype>=0 && solvertype<=3, "FitSphereX: ProblemType is neither 1, 2 or 3");
+            alglib.ap.assert(math.isfinite(penalty) && (double)(penalty)>=(double)(0), "FitSphereX: Penalty<0 or is not finite");
+            alglib.ap.assert(math.isfinite(epsx) && (double)(epsx)>=(double)(0), "FitSphereX: EpsX<0 or is not finite");
+            alglib.ap.assert(aulits>=0, "FitSphereX: AULIts<0");
+            if( solvertype==0 )
+            {
+                solvertype = 1;
+            }
+            if( (double)(penalty)==(double)(0) )
+            {
+                penalty = 1.0E6;
+            }
+            if( (double)(epsx)==(double)(0) )
+            {
+                epsx = 1.0E-12;
+            }
+            if( aulits==0 )
+            {
+                aulits = 20;
+            }
+            safeguard = 10;
+            maxouterits = 10;
+            maxits = 10000;
+            rep.nfev = 0;
+            rep.iterationscount = 0;
+            
+            //
+            // Determine initial values, initial estimates and spread of the points
+            //
+            vmin = new double[nx];
+            vmax = new double[nx];
+            cx = new double[nx];
+            for(j=0; j<=nx-1; j++)
+            {
+                vmin[j] = xy[0,j];
+                vmax[j] = xy[0,j];
+                cx[j] = 0;
+            }
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=nx-1; j++)
+                {
+                    cx[j] = cx[j]+xy[i,j];
+                    vmin[j] = Math.Min(vmin[j], xy[i,j]);
+                    vmax[j] = Math.Max(vmax[j], xy[i,j]);
+                }
+            }
+            spread = 0;
+            for(j=0; j<=nx-1; j++)
+            {
+                cx[j] = cx[j]/npoints;
+                spread = Math.Max(spread, vmax[j]-vmin[j]);
+            }
+            rlo = math.maxrealnumber;
+            rhi = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                v = 0;
+                for(j=0; j<=nx-1; j++)
+                {
+                    v = v+math.sqr(xy[i,j]-cx[j]);
+                }
+                v = Math.Sqrt(v);
+                rhi = Math.Max(rhi, v);
+                rlo = Math.Min(rlo, v);
+            }
+            
+            //
+            // Handle degenerate case of zero spread
+            //
+            if( (double)(spread)==(double)(0) )
+            {
+                for(j=0; j<=nx-1; j++)
+                {
+                    cx[j] = vmin[j];
+                }
+                rhi = 0;
+                rlo = 0;
+                return;
+            }
+            
+            //
+            // Prepare initial point for optimizer, scale vector and box constraints
+            //
+            pcr = new double[nx+2];
+            scr = new double[nx+2];
+            bl = new double[nx+2];
+            bu = new double[nx+2];
+            for(j=0; j<=nx-1; j++)
+            {
+                pcr[j] = cx[j];
+                scr[j] = 0.1*spread;
+                bl[j] = cx[j]-safeguard*spread;
+                bu[j] = cx[j]+safeguard*spread;
+            }
+            pcr[nx+0] = rlo;
+            pcr[nx+1] = rhi;
+            scr[nx+0] = 0.5*spread;
+            scr[nx+1] = 0.5*spread;
+            bl[nx+0] = 0;
+            bl[nx+1] = 0;
+            bu[nx+0] = safeguard*rhi;
+            bu[nx+1] = safeguard*rhi;
+            
+            //
+            // First branch: least squares fitting vs MI/MC/MZ fitting
+            //
+            if( problemtype==0 )
+            {
+                
+                //
+                // Solve problem with Levenberg-Marquardt algorithm
+                //
+                pcr[nx] = rhi;
+                minlm.minlmcreatevj(nx+1, npoints, pcr, lmstate, _params);
+                minlm.minlmsetscale(lmstate, scr, _params);
+                minlm.minlmsetbc(lmstate, bl, bu, _params);
+                minlm.minlmsetcond(lmstate, epsx, maxits, _params);
+                while( minlm.minlmiteration(lmstate, _params) )
+                {
+                    if( lmstate.needfij || lmstate.needfi )
+                    {
+                        apserv.inc(ref rep.nfev, _params);
+                        for(i=0; i<=npoints-1; i++)
+                        {
+                            v = 0;
+                            for(j=0; j<=nx-1; j++)
+                            {
+                                v = v+math.sqr(lmstate.x[j]-xy[i,j]);
+                            }
+                            lmstate.fi[i] = Math.Sqrt(v)-lmstate.x[nx];
+                            if( lmstate.needfij )
+                            {
+                                for(j=0; j<=nx-1; j++)
+                                {
+                                    lmstate.j[i,j] = 0.5/(1.0E-9*spread+Math.Sqrt(v))*2*(lmstate.x[j]-xy[i,j]);
+                                }
+                                lmstate.j[i,nx] = -1;
+                            }
+                        }
+                        continue;
+                    }
+                    alglib.ap.assert(false);
+                }
+                minlm.minlmresults(lmstate, ref pcr, lmrep, _params);
+                alglib.ap.assert(lmrep.terminationtype>0, "FitSphereX: unexpected failure of LM solver");
+                rep.iterationscount = rep.iterationscount+lmrep.iterationscount;
+                
+                //
+                // Offload center coordinates from PCR to CX,
+                // re-calculate exact value of RLo/RHi using CX.
+                //
+                for(j=0; j<=nx-1; j++)
+                {
+                    cx[j] = pcr[j];
+                }
+                vv = 0;
+                for(i=0; i<=npoints-1; i++)
+                {
+                    v = 0;
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        v = v+math.sqr(xy[i,j]-cx[j]);
+                    }
+                    v = Math.Sqrt(v);
+                    vv = vv+v/npoints;
+                }
+                rlo = vv;
+                rhi = vv;
+            }
+            else
+            {
+                
+                //
+                // MI, MC, MZ fitting.
+                // Prepare problem metrics
+                //
+                userlo = problemtype==2 || problemtype==3;
+                userhi = problemtype==1 || problemtype==3;
+                if( userlo && userhi )
+                {
+                    cpr = 2;
+                }
+                else
+                {
+                    cpr = 1;
+                }
+                if( userlo )
+                {
+                    vlo = 1;
+                }
+                else
+                {
+                    vlo = 0;
+                }
+                if( userhi )
+                {
+                    vhi = 1;
+                }
+                else
+                {
+                    vhi = 0;
+                }
+                
+                //
+                // Solve with NLC solver; problem is treated as general nonlinearly constrained
+                // programming, with augmented Lagrangian solver or SLP being used.
+                //
+                if( solvertype==1 || solvertype==3 )
+                {
+                    minnlc.minnlccreate(nx+2, pcr, nlcstate, _params);
+                    minnlc.minnlcsetscale(nlcstate, scr, _params);
+                    minnlc.minnlcsetbc(nlcstate, bl, bu, _params);
+                    minnlc.minnlcsetnlc(nlcstate, 0, cpr*npoints, _params);
+                    minnlc.minnlcsetcond(nlcstate, epsx, maxits, _params);
+                    minnlc.minnlcsetprecexactrobust(nlcstate, 5, _params);
+                    minnlc.minnlcsetstpmax(nlcstate, 0.1, _params);
+                    if( solvertype==1 )
+                    {
+                        minnlc.minnlcsetalgoaul(nlcstate, penalty, aulits, _params);
+                    }
+                    else
+                    {
+                        minnlc.minnlcsetalgoslp(nlcstate, _params);
+                    }
+                    minnlc.minnlcrestartfrom(nlcstate, pcr, _params);
+                    while( minnlc.minnlciteration(nlcstate, _params) )
+                    {
+                        if( nlcstate.needfij )
+                        {
+                            apserv.inc(ref rep.nfev, _params);
+                            nlcstate.fi[0] = vhi*nlcstate.x[nx+1]-vlo*nlcstate.x[nx+0];
+                            for(j=0; j<=nx-1; j++)
+                            {
+                                nlcstate.j[0,j] = 0;
+                            }
+                            nlcstate.j[0,nx+0] = -(1*vlo);
+                            nlcstate.j[0,nx+1] = 1*vhi;
+                            for(i=0; i<=npoints-1; i++)
+                            {
+                                suboffset = 0;
+                                if( userhi )
+                                {
+                                    dstrow = 1+cpr*i+suboffset;
+                                    v = 0;
+                                    for(j=0; j<=nx-1; j++)
+                                    {
+                                        vv = nlcstate.x[j]-xy[i,j];
+                                        v = v+vv*vv;
+                                        nlcstate.j[dstrow,j] = 2*vv;
+                                    }
+                                    vv = nlcstate.x[nx+1];
+                                    v = v-vv*vv;
+                                    nlcstate.j[dstrow,nx+0] = 0;
+                                    nlcstate.j[dstrow,nx+1] = -(2*vv);
+                                    nlcstate.fi[dstrow] = v;
+                                    apserv.inc(ref suboffset, _params);
+                                }
+                                if( userlo )
+                                {
+                                    dstrow = 1+cpr*i+suboffset;
+                                    v = 0;
+                                    for(j=0; j<=nx-1; j++)
+                                    {
+                                        vv = nlcstate.x[j]-xy[i,j];
+                                        v = v-vv*vv;
+                                        nlcstate.j[dstrow,j] = -(2*vv);
+                                    }
+                                    vv = nlcstate.x[nx+0];
+                                    v = v+vv*vv;
+                                    nlcstate.j[dstrow,nx+0] = 2*vv;
+                                    nlcstate.j[dstrow,nx+1] = 0;
+                                    nlcstate.fi[dstrow] = v;
+                                    apserv.inc(ref suboffset, _params);
+                                }
+                                alglib.ap.assert(suboffset==cpr);
+                            }
+                            continue;
+                        }
+                        alglib.ap.assert(false);
+                    }
+                    minnlc.minnlcresults(nlcstate, ref pcr, nlcrep, _params);
+                    alglib.ap.assert(nlcrep.terminationtype>0, "FitSphereX: unexpected failure of NLC solver");
+                    rep.iterationscount = rep.iterationscount+nlcrep.iterationscount;
+                    
+                    //
+                    // Offload center coordinates from PCR to CX,
+                    // re-calculate exact value of RLo/RHi using CX.
+                    //
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        cx[j] = pcr[j];
+                    }
+                    rlo = math.maxrealnumber;
+                    rhi = 0;
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        v = 0;
+                        for(j=0; j<=nx-1; j++)
+                        {
+                            v = v+math.sqr(xy[i,j]-cx[j]);
+                        }
+                        v = Math.Sqrt(v);
+                        rhi = Math.Max(rhi, v);
+                        rlo = Math.Min(rlo, v);
+                    }
+                    if( !userlo )
+                    {
+                        rlo = 0;
+                    }
+                    if( !userhi )
+                    {
+                        rhi = 0;
+                    }
+                    return;
+                }
+                
+                //
+                // Solve problem with SLP (sequential LP) approach; this approach
+                // is much faster than NLP, but often fails for MI and MC (for MZ
+                // it performs well enough).
+                //
+                // REFERENCE: "On a sequential linear programming approach to finding
+                //            the smallest circumscribed, largest inscribed, and minimum
+                //            zone circle or sphere", Helmuth Spath and G.A.Watson
+                //
+                if( solvertype==2 )
+                {
+                    cmatrix = new double[cpr*npoints, nx+3];
+                    ct = new int[cpr*npoints];
+                    prevc = new double[nx];
+                    minbleic.minbleiccreate(nx+2, pcr, blcstate, _params);
+                    minbleic.minbleicsetscale(blcstate, scr, _params);
+                    minbleic.minbleicsetbc(blcstate, bl, bu, _params);
+                    minbleic.minbleicsetcond(blcstate, 0, 0, epsx, maxits, _params);
+                    for(outeridx=0; outeridx<=maxouterits-1; outeridx++)
+                    {
+                        
+                        //
+                        // Prepare initial point for algorithm; center coordinates at
+                        // PCR are used to calculate RLo/RHi and update PCR with them.
+                        //
+                        rlo = math.maxrealnumber;
+                        rhi = 0;
+                        for(i=0; i<=npoints-1; i++)
+                        {
+                            v = 0;
+                            for(j=0; j<=nx-1; j++)
+                            {
+                                v = v+math.sqr(xy[i,j]-pcr[j]);
+                            }
+                            v = Math.Sqrt(v);
+                            rhi = Math.Max(rhi, v);
+                            rlo = Math.Min(rlo, v);
+                        }
+                        pcr[nx+0] = rlo*0.99999;
+                        pcr[nx+1] = rhi/0.99999;
+                        
+                        //
+                        // Generate matrix of linear constraints
+                        //
+                        for(i=0; i<=npoints-1; i++)
+                        {
+                            v = 0;
+                            for(j=0; j<=nx-1; j++)
+                            {
+                                v = v+math.sqr(xy[i,j]);
+                            }
+                            bi = -(v/2);
+                            suboffset = 0;
+                            if( userhi )
+                            {
+                                dstrow = cpr*i+suboffset;
+                                for(j=0; j<=nx-1; j++)
+                                {
+                                    cmatrix[dstrow,j] = pcr[j]/2-xy[i,j];
+                                }
+                                cmatrix[dstrow,nx+0] = 0;
+                                cmatrix[dstrow,nx+1] = -(rhi/2);
+                                cmatrix[dstrow,nx+2] = bi;
+                                ct[dstrow] = -1;
+                                apserv.inc(ref suboffset, _params);
+                            }
+                            if( userlo )
+                            {
+                                dstrow = cpr*i+suboffset;
+                                for(j=0; j<=nx-1; j++)
+                                {
+                                    cmatrix[dstrow,j] = -(pcr[j]/2-xy[i,j]);
+                                }
+                                cmatrix[dstrow,nx+0] = rlo/2;
+                                cmatrix[dstrow,nx+1] = 0;
+                                cmatrix[dstrow,nx+2] = -bi;
+                                ct[dstrow] = -1;
+                                apserv.inc(ref suboffset, _params);
+                            }
+                            alglib.ap.assert(suboffset==cpr);
+                        }
+                        
+                        //
+                        // Solve LP subproblem with MinBLEIC
+                        //
+                        for(j=0; j<=nx-1; j++)
+                        {
+                            prevc[j] = pcr[j];
+                        }
+                        minbleic.minbleicsetlc(blcstate, cmatrix, ct, cpr*npoints, _params);
+                        minbleic.minbleicrestartfrom(blcstate, pcr, _params);
+                        while( minbleic.minbleiciteration(blcstate, _params) )
+                        {
+                            if( blcstate.needfg )
+                            {
+                                apserv.inc(ref rep.nfev, _params);
+                                blcstate.f = vhi*blcstate.x[nx+1]-vlo*blcstate.x[nx+0];
+                                for(j=0; j<=nx-1; j++)
+                                {
+                                    blcstate.g[j] = 0;
+                                }
+                                blcstate.g[nx+0] = -(1*vlo);
+                                blcstate.g[nx+1] = 1*vhi;
+                                continue;
+                            }
+                        }
+                        minbleic.minbleicresults(blcstate, ref pcr, blcrep, _params);
+                        alglib.ap.assert(blcrep.terminationtype>0, "FitSphereX: unexpected failure of BLEIC solver");
+                        rep.iterationscount = rep.iterationscount+blcrep.iterationscount;
+                        
+                        //
+                        // Terminate iterations early if we converged
+                        //
+                        v = 0;
+                        for(j=0; j<=nx-1; j++)
+                        {
+                            v = v+math.sqr(prevc[j]-pcr[j]);
+                        }
+                        v = Math.Sqrt(v);
+                        if( (double)(v)<=(double)(epsx) )
+                        {
+                            break;
+                        }
+                    }
+                    
+                    //
+                    // Offload center coordinates from PCR to CX,
+                    // re-calculate exact value of RLo/RHi using CX.
+                    //
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        cx[j] = pcr[j];
+                    }
+                    rlo = math.maxrealnumber;
+                    rhi = 0;
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        v = 0;
+                        for(j=0; j<=nx-1; j++)
+                        {
+                            v = v+math.sqr(xy[i,j]-cx[j]);
+                        }
+                        v = Math.Sqrt(v);
+                        rhi = Math.Max(rhi, v);
+                        rlo = Math.Min(rlo, v);
+                    }
+                    if( !userlo )
+                    {
+                        rlo = 0;
+                    }
+                    if( !userhi )
+                    {
+                        rhi = 0;
+                    }
+                    return;
+                }
+                
+                //
+                // Oooops...!
+                //
+                alglib.ap.assert(false, "FitSphereX: integrity check failed");
+            }
+        }
+
+
+    }
+    public class intfitserv
+    {
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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");
+            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;
+                    }
+                }
+            }
+            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]);
+            }
+            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);
+                }
+            }
+            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;
+                }
+            }
+        }
+
+
+        public static void buildpriorterm(double[,] xy,
+            int n,
+            int nx,
+            int ny,
+            int modeltype,
+            double priorval,
+            ref double[,] v,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int j0 = 0;
+            int j1 = 0;
+            double rj = 0;
+            double[,] araw = new double[0,0];
+            double[,] amod = new double[0,0];
+            double[,] braw = new double[0,0];
+            double[] tmp0 = new double[0];
+            double lambdareg = 0;
+            int rfsits = 0;
+
+            v = new double[0,0];
+
+            alglib.ap.assert(n>=0, "BuildPriorTerm: N<0");
+            alglib.ap.assert(nx>0, "BuildPriorTerm: NX<=0");
+            alglib.ap.assert(ny>0, "BuildPriorTerm: NY<=0");
+            v = new double[ny, nx+1];
+            for(i=0; i<=alglib.ap.rows(v)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(v)-1; j++)
+                {
+                    v[i,j] = 0;
+                }
+            }
+            if( n==0 )
+            {
+                if( modeltype==0 )
+                {
+                    for(i=0; i<=ny-1; i++)
+                    {
+                        v[i,nx] = priorval;
+                    }
+                    return;
+                }
+                if( modeltype==1 )
+                {
+                    return;
+                }
+                if( modeltype==2 )
+                {
+                    return;
+                }
+                if( modeltype==3 )
+                {
+                    return;
+                }
+                alglib.ap.assert(false, "BuildPriorTerm: unexpected model type");
+            }
+            if( modeltype==0 )
+            {
+                for(i=0; i<=ny-1; i++)
+                {
+                    v[i,nx] = priorval;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        xy[i,nx+j] = xy[i,nx+j]-priorval;
+                    }
+                }
+                return;
+            }
+            if( modeltype==2 )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        v[j,nx] = v[j,nx]+xy[i,nx+j];
+                    }
+                }
+                for(j=0; j<=ny-1; j++)
+                {
+                    v[j,nx] = v[j,nx]/apserv.coalesce(n, 1, _params);
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        xy[i,nx+j] = xy[i,nx+j]-v[j,nx];
+                    }
+                }
+                return;
+            }
+            if( modeltype==3 )
+            {
+                return;
+            }
+            alglib.ap.assert(modeltype==1, "BuildPriorTerm: unexpected model type");
+            lambdareg = 0.0;
+            araw = new double[nx+1, nx+1];
+            braw = new double[nx+1, ny];
+            tmp0 = new double[nx+1];
+            amod = new double[nx+1, nx+1];
+            for(i=0; i<=nx; i++)
+            {
+                for(j=0; j<=nx; j++)
+                {
+                    araw[i,j] = 0;
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=nx-1; j++)
+                {
+                    tmp0[j] = xy[i,j];
+                }
+                tmp0[nx] = 1.0;
+                for(j0=0; j0<=nx; j0++)
+                {
+                    for(j1=0; j1<=nx; j1++)
+                    {
+                        araw[j0,j1] = araw[j0,j1]+tmp0[j0]*tmp0[j1];
+                    }
+                }
+            }
+            for(rfsits=1; rfsits<=3; rfsits++)
+            {
+                for(i=0; i<=nx; i++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        braw[i,j] = 0;
+                    }
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        tmp0[j] = xy[i,j];
+                    }
+                    tmp0[nx] = 1.0;
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        rj = xy[i,nx+j];
+                        for(j0=0; j0<=nx; j0++)
+                        {
+                            rj = rj-tmp0[j0]*v[j,j0];
+                        }
+                        for(j0=0; j0<=nx; j0++)
+                        {
+                            braw[j0,j] = braw[j0,j]+rj*tmp0[j0];
+                        }
+                    }
+                }
+                while( true )
+                {
+                    for(i=0; i<=nx; i++)
+                    {
+                        for(j=0; j<=nx; j++)
+                        {
+                            amod[i,j] = araw[i,j];
+                        }
+                        amod[i,i] = amod[i,i]+lambdareg*apserv.coalesce(amod[i,i], 1, _params);
+                    }
+                    if( trfac.spdmatrixcholesky(ref amod, nx+1, true, _params) )
+                    {
+                        break;
+                    }
+                    lambdareg = apserv.coalesce(10*lambdareg, 1.0E-12, _params);
+                }
+                ablas.rmatrixlefttrsm(nx+1, ny, amod, 0, 0, true, false, 1, braw, 0, 0, _params);
+                ablas.rmatrixlefttrsm(nx+1, ny, amod, 0, 0, true, false, 0, braw, 0, 0, _params);
+                for(i=0; i<=nx; i++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        v[j,i] = v[j,i]+braw[i,j];
+                    }
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=nx-1; j++)
+                {
+                    tmp0[j] = xy[i,j];
+                }
+                tmp0[nx] = 1.0;
+                for(j=0; j<=ny-1; j++)
+                {
+                    rj = 0.0;
+                    for(j0=0; j0<=nx; j0++)
+                    {
+                        rj = rj+tmp0[j0]*v[j,j0];
+                    }
+                    xy[i,nx+j] = xy[i,nx+j]-rj;
+                }
+            }
+        }
+
+
+        public static void buildpriorterm1(double[] xy1,
+            int n,
+            int nx,
+            int ny,
+            int modeltype,
+            double priorval,
+            ref double[,] v,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int ew = 0;
+            double rj = 0;
+            double[,] araw = new double[0,0];
+            double[,] amod = new double[0,0];
+            double[,] braw = new double[0,0];
+            double[] tmp0 = new double[0];
+            double lambdareg = 0;
+            int rfsits = 0;
+
+            v = new double[0,0];
+
+            alglib.ap.assert(n>=0, "BuildPriorTerm: N<0");
+            alglib.ap.assert(nx>0, "BuildPriorTerm: NX<=0");
+            alglib.ap.assert(ny>0, "BuildPriorTerm: NY<=0");
+            ew = nx+ny;
+            v = new double[ny, nx+1];
+            for(i=0; i<=alglib.ap.rows(v)-1; i++)
+            {
+                for(j=0; j<=alglib.ap.cols(v)-1; j++)
+                {
+                    v[i,j] = 0;
+                }
+            }
+            if( n==0 )
+            {
+                if( modeltype==0 )
+                {
+                    for(i=0; i<=ny-1; i++)
+                    {
+                        v[i,nx] = priorval;
+                    }
+                    return;
+                }
+                if( modeltype==1 )
+                {
+                    return;
+                }
+                if( modeltype==2 )
+                {
+                    return;
+                }
+                if( modeltype==3 )
+                {
+                    return;
+                }
+                alglib.ap.assert(false, "BuildPriorTerm: unexpected model type");
+            }
+            if( modeltype==0 )
+            {
+                for(i=0; i<=ny-1; i++)
+                {
+                    v[i,nx] = priorval;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        xy1[i*ew+nx+j] = xy1[i*ew+nx+j]-priorval;
+                    }
+                }
+                return;
+            }
+            if( modeltype==2 )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        v[j,nx] = v[j,nx]+xy1[i*ew+nx+j];
+                    }
+                }
+                for(j=0; j<=ny-1; j++)
+                {
+                    v[j,nx] = v[j,nx]/apserv.coalesce(n, 1, _params);
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        xy1[i*ew+nx+j] = xy1[i*ew+nx+j]-v[j,nx];
+                    }
+                }
+                return;
+            }
+            if( modeltype==3 )
+            {
+                return;
+            }
+            alglib.ap.assert(modeltype==1, "BuildPriorTerm: unexpected model type");
+            lambdareg = 0.0;
+            araw = new double[nx+1, nx+1];
+            braw = new double[nx+1, ny];
+            tmp0 = new double[nx+1];
+            amod = new double[nx+1, nx+1];
+            for(i=0; i<=nx; i++)
+            {
+                for(j=0; j<=nx; j++)
+                {
+                    araw[i,j] = 0;
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=nx-1; j++)
+                {
+                    tmp0[j] = xy1[i*ew+j];
+                }
+                tmp0[nx] = 1.0;
+                for(j0=0; j0<=nx; j0++)
+                {
+                    for(j1=0; j1<=nx; j1++)
+                    {
+                        araw[j0,j1] = araw[j0,j1]+tmp0[j0]*tmp0[j1];
+                    }
+                }
+            }
+            for(rfsits=1; rfsits<=3; rfsits++)
+            {
+                for(i=0; i<=nx; i++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        braw[i,j] = 0;
+                    }
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        tmp0[j] = xy1[i*ew+j];
+                    }
+                    tmp0[nx] = 1.0;
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        rj = xy1[i*ew+nx+j];
+                        for(j0=0; j0<=nx; j0++)
+                        {
+                            rj = rj-tmp0[j0]*v[j,j0];
+                        }
+                        for(j0=0; j0<=nx; j0++)
+                        {
+                            braw[j0,j] = braw[j0,j]+rj*tmp0[j0];
+                        }
+                    }
+                }
+                while( true )
+                {
+                    for(i=0; i<=nx; i++)
+                    {
+                        for(j=0; j<=nx; j++)
+                        {
+                            amod[i,j] = araw[i,j];
+                        }
+                        amod[i,i] = amod[i,i]+lambdareg*apserv.coalesce(amod[i,i], 1, _params);
+                    }
+                    if( trfac.spdmatrixcholesky(ref amod, nx+1, true, _params) )
+                    {
+                        break;
+                    }
+                    lambdareg = apserv.coalesce(10*lambdareg, 1.0E-12, _params);
+                }
+                ablas.rmatrixlefttrsm(nx+1, ny, amod, 0, 0, true, false, 1, braw, 0, 0, _params);
+                ablas.rmatrixlefttrsm(nx+1, ny, amod, 0, 0, true, false, 0, braw, 0, 0, _params);
+                for(i=0; i<=nx; i++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        v[j,i] = v[j,i]+braw[i,j];
+                    }
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=nx-1; j++)
+                {
+                    tmp0[j] = xy1[i*ew+j];
+                }
+                tmp0[nx] = 1.0;
+                for(j=0; j<=ny-1; j++)
+                {
+                    rj = 0.0;
+                    for(j0=0; j0<=nx; j0++)
+                    {
+                        rj = rj+tmp0[j0]*v[j,j0];
+                    }
+                    xy1[i*ew+nx+j] = xy1[i*ew+nx+j]-rj;
+                }
+            }
+        }
+
+
+    }
+    public class spline1d
+    {
+        /*************************************************************************
+        1-dimensional spline interpolant
+        *************************************************************************/
+        public class spline1dinterpolant : apobject
+        {
+            public bool periodic;
+            public int n;
+            public int k;
+            public int continuity;
+            public double[] x;
+            public double[] c;
+            public spline1dinterpolant()
+            {
+                init();
+            }
+            public override void init()
+            {
+                x = new double[0];
+                c = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                spline1dinterpolant _result = new spline1dinterpolant();
+                _result.periodic = periodic;
+                _result.n = n;
+                _result.k = k;
+                _result.continuity = continuity;
+                _result.x = (double[])x.Clone();
+                _result.c = (double[])c.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        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 : apobject
+        {
+            public double taskrcond;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public double maxerror;
+            public spline1dfitreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                spline1dfitreport _result = new spline1dfitreport();
+                _result.taskrcond = taskrcond;
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.avgrelerror = avgrelerror;
+                _result.maxerror = maxerror;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DBuildLinear: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "Spline1DBuildLinear: Y contains infinite or NAN values!");
+            heapsortpoints(ref x, ref y, n, _params);
+            alglib.ap.assert(apserv.aredistinct(x, n, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DBuildCubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, ylen, _params), "Spline1DBuildCubic: Y contains infinite or NAN values!");
+            heapsortppoints(ref x, ref y, ref p, n, _params);
+            alglib.ap.assert(apserv.aredistinct(x, n, _params), "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, _params);
+            spline1dbuildhermite(x, y, d, n, c, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DGridDiffCubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, ylen, _params), "Spline1DGridDiffCubic: Y contains infinite or NAN values!");
+            heapsortppoints(ref x, ref y, ref p, n, _params);
+            alglib.ap.assert(apserv.aredistinct(x, n, _params), "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, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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 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, _params), "Spline1DGridDiff2Cubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, ylen, _params), "Spline1DGridDiff2Cubic: Y contains infinite or NAN values!");
+            heapsortppoints(ref x, ref y, ref p, n, _params);
+            alglib.ap.assert(apserv.aredistinct(x, n, _params), "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, _params);
+            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;
+                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,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DConvCubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, ylen, _params), "Spline1DConvCubic: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(x2, n2, _params), "Spline1DConvCubic: X2 contains infinite or NAN values!");
+            heapsortppoints(ref x, ref y, ref p, n, _params);
+            alglib.ap.assert(apserv.aredistinct(x, n, _params), "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, _params);
+                    x2[i] = t;
+                }
+            }
+            heapsortppoints(ref x2, ref dt, ref p2, n2, _params);
+            
+            //
+            // 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, _params);
+            spline1dconvdiffinternal(x, y, d, n, x2, n2, ref y2, true, ref d1, false, ref d2, false, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DConvDiffCubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, ylen, _params), "Spline1DConvDiffCubic: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(x2, n2, _params), "Spline1DConvDiffCubic: X2 contains infinite or NAN values!");
+            heapsortppoints(ref x, ref y, ref p, n, _params);
+            alglib.ap.assert(apserv.aredistinct(x, n, _params), "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, _params);
+                    x2[i] = t;
+                }
+            }
+            heapsortppoints(ref x2, ref dt, ref p2, n2, _params);
+            
+            //
+            // 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, _params);
+            spline1dconvdiffinternal(x, y, d, n, x2, n2, ref y2, true, ref d2, true, ref rt1, false, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DConvDiff2Cubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, ylen, _params), "Spline1DConvDiff2Cubic: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(x2, n2, _params), "Spline1DConvDiff2Cubic: X2 contains infinite or NAN values!");
+            heapsortppoints(ref x, ref y, ref p, n, _params);
+            alglib.ap.assert(apserv.aredistinct(x, n, _params), "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, _params);
+                    x2[i] = t;
+                }
+            }
+            heapsortppoints(ref x2, ref dt, ref p2, n2, _params);
+            
+            //
+            // 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, _params);
+            spline1dconvdiffinternal(x, y, d, n, x2, n2, ref y2, true, ref d2, true, ref dd2, true, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DBuildCatmullRom: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "Spline1DBuildCatmullRom: Y contains infinite or NAN values!");
+            heapsortpoints(ref x, ref y, n, _params);
+            alglib.ap.assert(apserv.aredistinct(x, n, _params), "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, _params);
+                return;
+            }
+            if( n==2 && boundtype==-1 )
+            {
+                
+                //
+                // Same as cubic spline with periodic conditions
+                //
+                spline1dbuildcubic(x, y, n, -1, 0.0, -1, 0.0, c, _params);
+                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, _params);
+                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, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DBuildHermite: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "Spline1DBuildHermite: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(d, n, _params), "Spline1DBuildHermite: D contains infinite or NAN values!");
+            heapsortdpoints(ref x, ref y, ref d, n, _params);
+            alglib.ap.assert(apserv.aredistinct(x, n, _params), "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>=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 spline1dbuildakima(double[] x,
+            double[] y,
+            int n,
+            spline1dinterpolant c,
+            alglib.xparams _params)
+        {
+            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>=2, "Spline1DBuildAkima: N<2!");
+            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, _params), "Spline1DBuildAkima: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "Spline1DBuildAkima: Y contains infinite or NAN values!");
+            heapsortpoints(ref x, ref y, n, _params);
+            alglib.ap.assert(apserv.aredistinct(x, n, _params), "Spline1DBuildAkima: at least two consequent points are too close!");
+            
+            //
+            // Handle special cases: N=2, N=3, N=4
+            //
+            if( n<=4 )
+            {
+                spline1dbuildcubic(x, y, n, 0, 0.0, 0, 0.0, c, _params);
+                return;
+            }
+            
+            //
+            // 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], _params);
+            d[1] = diffthreepoint(x[1], x[0], y[0], x[1], y[1], x[2], y[2], _params);
+            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], _params);
+            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], _params);
+            
+            //
+            // Build Akima spline using Hermite interpolation scheme
+            //
+            spline1dbuildhermite(x, y, d, n, c, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            }
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            }
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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
+
+        OUTPUT PARAMETERS:
+            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]
+                        
+        NOTE:
+            You  can rebuild spline with  Spline1DBuildHermite()  function,  which
+            accepts as inputs function values and derivatives at nodes, which  are
+            easy to calculate when you have coefficients.
+
+          -- ALGLIB PROJECT --
+             Copyright 29.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dunpack(spline1dinterpolant c,
+            ref int n,
+            ref double[,] tbl,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+                    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, _params);
+            }
+            else
+            {
+                spline1dbuildlinear(x, y, n, c, _params);
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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;
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+                   
+        INPUT PARAMETERS:
+            X   -   points, array[0..N-1].
+            Y   -   function values, array[0..N-1].
+            N   -   number of points (optional):
+                    * N>0
+                    * if given, only first N elements of X/Y are processed
+                    * if not given, automatically determined from X/Y 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 18.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dfitpenalized(double[] x,
+            double[] y,
+            int n,
+            int m,
+            double rho,
+            ref int info,
+            spline1dinterpolant s,
+            spline1dfitreport rep,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DFitPenalized: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+                   
+        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,
+            spline1dinterpolant s,
+            spline1dfitreport rep,
+            alglib.xparams _params)
+        {
+            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];
+            spline1dinterpolant bs = new 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, _params), "Spline1DFitPenalizedW: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "Spline1DFitPenalizedW: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(w, n, _params), "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
+            //
+            heapsortdpoints(ref x, ref y, ref w, n, _params);
+            
+            //
+            // Scale X, Y, XC, YC
+            //
+            intfitserv.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, _params);
+            
+            //
+            // 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;
+                spline1dgriddiff2cubic(bx, by, m, 2, 0.0, 2, 0.0, ref bd1, ref bd2, _params);
+                spline1dbuildcubic(bx, by, m, 2, 0.0, 2, 0.0, bs, _params);
+                
+                //
+                // Calculate B-th column of FMatrix
+                // Update FDMax (maximum column norm)
+                //
+                spline1dconvcubic(bx, by, m, 2, 0.0, 2, 0.0, x, n, ref fcolumn, _params);
+                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, nmatrix, 0, 0, _params);
+            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, _params) )
+            {
+                info = -4;
+                return;
+            }
+            fbls.fblscholeskysolve(nmatrix, 1.0, m, true, rightpart, ref tmp0, _params);
+            for(i_=0; i_<=m-1;i_++)
+            {
+                c[i_] = rightpart[i_];
+            }
+            
+            //
+            // add nodes to force linearity outside of the fitting interval
+            //
+            spline1dgriddiffcubic(bx, c, m, 2, 0.0, 2, 0.0, ref bd1, _params);
+            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];
+            spline1dbuildhermite(tx, ty, td, m+2, s, _params);
+            spline1dlintransx(s, 2/(xb-xa), -((xa+xb)/(xb-xa)), _params);
+            spline1dlintransy(s, sb-sa, sa, _params);
+            info = 1;
+            
+            //
+            // Fill report
+            //
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.avgrelerror = 0;
+            rep.maxerror = 0;
+            relcnt = 0;
+            spline1dconvcubic(bx, rightpart, m, 2, 0.0, 2, 0.0, x, n, ref fcolumn, _params);
+            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;
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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), _params);
+                apserv.rvectorsetlengthatleast(ref tmpe, 2*(c.n-1), _params);
+                apserv.ivectorsetlengthatleast(ref tmpet, 2*(c.n-1), _params);
+                
+                //
+                //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, _params);
+                    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, _params);
+                apserv.rvectorsetlengthatleast(ref e, ne, _params);
+                apserv.ivectorsetlengthatleast(ref et, ne, _params);
+                
+                //
+                //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, _params);
+                apserv.rvectorsetlengthatleast(ref tmpc, 4*c.n, _params);
+                apserv.rvectorsetlengthatleast(ref tmpe, 2*c.n, _params);
+                apserv.ivectorsetlengthatleast(ref tmpet, 2*c.n, _params);
+                
+                //
+                //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, _params);
+                    
+                    //
+                    //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, _params);
+                apserv.rvectorsetlengthatleast(ref e, ne, _params);
+                apserv.ivectorsetlengthatleast(ref et, ne, _params);
+                
+                //
+                //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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            
+            //
+            //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, _params);
+            ex0 = rescaleval(0, 1, a, b, ex0, _params);
+            ex1 = rescaleval(0, 1, a, b, ex1, _params);
+            
+            //
+            //case 3.1
+            //no extremums at [A;B]
+            //
+            if( ne==0 )
+            {
+                nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, 1, ref x0, _params);
+                if( nr==1 )
+                {
+                    x0 = rescaleval(0, 1, a, b, x0, _params);
+                }
+                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, _params);
+                    if( nr==1 )
+                    {
+                        x0 = rescaleval(0, 1, a, b, x0, _params);
+                    }
+                    return;
+                }
+                else
+                {
+                    nr = 0;
+                    i = 0;
+                    tex0 = rescaleval(a, b, 0, 1, ex0, _params);
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, tex0, ref x0, _params)+nr;
+                    if( nr>i )
+                    {
+                        tempdata[i] = rescaleval(0, tex0, a, ex0, x0, _params);
+                        i = i+1;
+                    }
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, tex0, 1, ref x0, _params)+nr;
+                    if( nr>i )
+                    {
+                        x0 = rescaleval(tex0, 1, ex0, b, x0, _params);
+                        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, _params);
+                    if( nr==1 )
+                    {
+                        x0 = rescaleval(0, 1, a, b, x0, _params);
+                    }
+                    return;
+                }
+                if( (double)(ex0)==(double)(a) && (double)(ex1)!=(double)(b) )
+                {
+                    nr = 0;
+                    i = 0;
+                    tex1 = rescaleval(a, b, 0, 1, ex1, _params);
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, tex1, ref x0, _params)+nr;
+                    if( nr>i )
+                    {
+                        tempdata[i] = rescaleval(0, tex1, a, ex1, x0, _params);
+                        i = i+1;
+                    }
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, tex1, 1, ref x0, _params)+nr;
+                    if( nr>i )
+                    {
+                        x0 = rescaleval(tex1, 1, ex1, b, x0, _params);
+                        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, _params);
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, tex0, ref x0, _params)+nr;
+                    if( nr>i )
+                    {
+                        tempdata[i] = rescaleval(0, tex0, a, ex0, x0, _params);
+                        i = i+1;
+                    }
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, tex0, 1, ref x0, _params)+nr;
+                    if( nr>i )
+                    {
+                        x0 = rescaleval(tex0, 1, ex0, b, x0, _params);
+                        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, _params);
+                    tex1 = rescaleval(a, b, 0, 1, ex1, _params);
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, 0, tex0, ref x0, _params)+nr;
+                    if( nr>i )
+                    {
+                        tempdata[i] = rescaleval(0, tex0, a, ex0, x0, _params);
+                        i = i+1;
+                    }
+                    nr = bisectmethod(pa, tmpma, pb, tmpmb, tex0, tex1, ref x0, _params)+nr;
+                    if( nr>i )
+                    {
+                        x0 = rescaleval(tex0, tex1, ex0, ex1, x0, _params);
+                        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, _params)+nr;
+                    if( nr>i )
+                    {
+                        x0 = rescaleval(tex1, 1, ex1, b, x0, _params);
+                        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            hermitecalc(pa, ma, pb, mb, b, ref rf, ref vacuum, _params);
+            
+            //
+            //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, _params);
+                hermitecalc(pa, ma, pb, mb, b0, ref rf, ref vacuum, _params);
+                hermitecalc(pa, ma, pb, mb, m, ref mf, ref vacuum, _params);
+                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;
+        }
+
+
+        /*************************************************************************
+        This function builds monotone cubic Hermite interpolant. This interpolant
+        is monotonic in [x(0),x(n-1)] and is constant outside of this interval.
+
+        In  case  y[]  form  non-monotonic  sequence,  interpolant  is  piecewise
+        monotonic.  Say, for x=(0,1,2,3,4)  and  y=(0,1,2,1,0)  interpolant  will
+        monotonically grow at [0..2] and monotonically decrease at [2..4].
+
+        INPUT PARAMETERS:
+            X           -   spline nodes, array[0..N-1]. Subroutine automatically
+                            sorts points, so caller may pass unsorted array.
+            Y           -   function values, array[0..N-1]
+            N           -   the number of points(N>=2).
+
+        OUTPUT PARAMETERS:
+            C           -   spline interpolant.
+
+         -- ALGLIB PROJECT --
+             Copyright 21.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline1dbuildmonotone(double[] x,
+            double[] y,
+            int n,
+            spline1dinterpolant c,
+            alglib.xparams _params)
+        {
+            double[] d = new double[0];
+            double[] ex = new double[0];
+            double[] ey = new double[0];
+            int[] p = new int[0];
+            double delta = 0;
+            double alpha = 0;
+            double beta = 0;
+            int tmpn = 0;
+            int sn = 0;
+            double ca = 0;
+            double cb = 0;
+            double epsilon = 0;
+            int i = 0;
+            int j = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+
+            
+            //
+            // Check lengths of arguments
+            //
+            alglib.ap.assert(n>=2, "Spline1DBuildMonotone: N<2");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "Spline1DBuildMonotone: Length(X)<N");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "Spline1DBuildMonotone: Length(Y)<N");
+            
+            //
+            // Check and sort points
+            //
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "Spline1DBuildMonotone: X contains infinite or NAN values");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "Spline1DBuildMonotone: Y contains infinite or NAN values");
+            heapsortppoints(ref x, ref y, ref p, n, _params);
+            alglib.ap.assert(apserv.aredistinct(x, n, _params), "Spline1DBuildMonotone: at least two consequent points are too close");
+            epsilon = math.machineepsilon;
+            n = n+2;
+            d = new double[n];
+            ex = new double[n];
+            ey = new double[n];
+            ex[0] = x[0]-Math.Abs(x[1]-x[0]);
+            ex[n-1] = x[n-3]+Math.Abs(x[n-3]-x[n-4]);
+            ey[0] = y[0];
+            ey[n-1] = y[n-3];
+            for(i=1; i<=n-2; i++)
+            {
+                ex[i] = x[i-1];
+                ey[i] = y[i-1];
+            }
+            
+            //
+            // Init sign of the function for first segment
+            //
+            i = 0;
+            ca = 0;
+            do
+            {
+                ca = ey[i+1]-ey[i];
+                i = i+1;
+            }
+            while( !((double)(ca)!=(double)(0) || i>n-2) );
+            if( (double)(ca)!=(double)(0) )
+            {
+                ca = ca/Math.Abs(ca);
+            }
+            i = 0;
+            while( i<n-1 )
+            {
+                
+                //
+                // Partition of the segment [X0;Xn]
+                //
+                tmpn = 1;
+                for(j=i; j<=n-2; j++)
+                {
+                    cb = ey[j+1]-ey[j];
+                    if( (double)(ca*cb)>=(double)(0) )
+                    {
+                        tmpn = tmpn+1;
+                    }
+                    else
+                    {
+                        ca = cb/Math.Abs(cb);
+                        break;
+                    }
+                }
+                sn = i+tmpn;
+                alglib.ap.assert(tmpn>=2, "Spline1DBuildMonotone: internal error");
+                
+                //
+                // Calculate derivatives for current segment
+                //
+                d[i] = 0;
+                d[sn-1] = 0;
+                for(j=i+1; j<=sn-2; j++)
+                {
+                    d[j] = ((ey[j]-ey[j-1])/(ex[j]-ex[j-1])+(ey[j+1]-ey[j])/(ex[j+1]-ex[j]))/2;
+                }
+                for(j=i; j<=sn-2; j++)
+                {
+                    delta = (ey[j+1]-ey[j])/(ex[j+1]-ex[j]);
+                    if( (double)(Math.Abs(delta))<=(double)(epsilon) )
+                    {
+                        d[j] = 0;
+                        d[j+1] = 0;
+                    }
+                    else
+                    {
+                        alpha = d[j]/delta;
+                        beta = d[j+1]/delta;
+                        if( (double)(alpha)!=(double)(0) )
+                        {
+                            cb = alpha*Math.Sqrt(1+math.sqr(beta/alpha));
+                        }
+                        else
+                        {
+                            if( (double)(beta)!=(double)(0) )
+                            {
+                                cb = beta;
+                            }
+                            else
+                            {
+                                continue;
+                            }
+                        }
+                        if( (double)(cb)>(double)(3) )
+                        {
+                            d[j] = 3*alpha*delta/cb;
+                            d[j+1] = 3*beta*delta/cb;
+                        }
+                    }
+                }
+                
+                //
+                // Transition to next segment
+                //
+                i = sn-1;
+            }
+            spline1dbuildhermite(ex, ey, d, n, c, _params);
+            c.continuity = 2;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Internal subroutine. Heap sort.
+        *************************************************************************/
+        private static void heapsortpoints(ref double[] x,
+            ref double[] y,
+            int n,
+            alglib.xparams _params)
+        {
+            double[] bufx = new double[0];
+            double[] bufy = new double[0];
+
+            tsort.tagsortfastr(ref x, ref y, ref bufx, ref bufy, n, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            solvetridiagonal(a, b, c, u, n, ref z, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 parametric
+    {
+        /*************************************************************************
+        Parametric spline inteprolant: 2-dimensional curve.
+
+        You should not try to access its members directly - use PSpline2XXXXXXXX()
+        functions instead.
+        *************************************************************************/
+        public class pspline2interpolant : apobject
+        {
+            public int n;
+            public bool periodic;
+            public double[] p;
+            public spline1d.spline1dinterpolant x;
+            public spline1d.spline1dinterpolant y;
+            public pspline2interpolant()
+            {
+                init();
+            }
+            public override void init()
+            {
+                p = new double[0];
+                x = new spline1d.spline1dinterpolant();
+                y = new spline1d.spline1dinterpolant();
+            }
+            public override alglib.apobject make_copy()
+            {
+                pspline2interpolant _result = new pspline2interpolant();
+                _result.n = n;
+                _result.periodic = periodic;
+                _result.p = (double[])p.Clone();
+                _result.x = (spline1d.spline1dinterpolant)x.make_copy();
+                _result.y = (spline1d.spline1dinterpolant)y.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Parametric spline inteprolant: 3-dimensional curve.
+
+        You should not try to access its members directly - use PSpline3XXXXXXXX()
+        functions instead.
+        *************************************************************************/
+        public class pspline3interpolant : apobject
+        {
+            public int n;
+            public bool periodic;
+            public double[] p;
+            public spline1d.spline1dinterpolant x;
+            public spline1d.spline1dinterpolant y;
+            public spline1d.spline1dinterpolant z;
+            public pspline3interpolant()
+            {
+                init();
+            }
+            public override void init()
+            {
+                p = new double[0];
+                x = new spline1d.spline1dinterpolant();
+                y = new spline1d.spline1dinterpolant();
+                z = new spline1d.spline1dinterpolant();
+            }
+            public override alglib.apobject make_copy()
+            {
+                pspline3interpolant _result = new pspline3interpolant();
+                _result.n = n;
+                _result.periodic = periodic;
+                _result.p = (double[])p.Clone();
+                _result.x = (spline1d.spline1dinterpolant)x.make_copy();
+                _result.y = (spline1d.spline1dinterpolant)y.make_copy();
+                _result.z = (spline1d.spline1dinterpolant)z.make_copy();
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            alglib.ap.assert(apserv.aredistinct(p.p, n, _params), "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, _params);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,1];
+                }
+                spline1d.spline1dbuildakima(p.p, tmp, n, p.y, _params);
+            }
+            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, _params);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,1];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n, 0, 0.0, p.y, _params);
+            }
+            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, _params);
+                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, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            alglib.ap.assert(apserv.aredistinct(p.p, n, _params), "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, _params);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,1];
+                }
+                spline1d.spline1dbuildakima(p.p, tmp, n, p.y, _params);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,2];
+                }
+                spline1d.spline1dbuildakima(p.p, tmp, n, p.z, _params);
+            }
+            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, _params);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,1];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n, 0, 0.0, p.y, _params);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    tmp[i_] = xy[i_,2];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n, 0, 0.0, p.z, _params);
+            }
+            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, _params);
+                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, _params);
+                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, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            alglib.ap.assert(apserv.aredistinct(p.p, n+1, _params), "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, _params);
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,1];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n+1, -1, 0.0, p.y, _params);
+            }
+            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, _params);
+                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, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            alglib.ap.assert(apserv.aredistinct(p.p, n+1, _params), "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, _params);
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,1];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n+1, -1, 0.0, p.y, _params);
+                for(i_=0; i_<=n;i_++)
+                {
+                    tmp[i_] = xyp[i_,2];
+                }
+                spline1d.spline1dbuildcatmullrom(p.p, tmp, n+1, -1, 0.0, p.z, _params);
+            }
+            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, _params);
+                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, _params);
+                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, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            x = 0;
+            y = 0;
+
+            if( p.periodic )
+            {
+                t = t-(int)Math.Floor(t);
+            }
+            x = spline1d.spline1dcalc(p.x, t, _params);
+            y = spline1d.spline1dcalc(p.y, t, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            x = 0;
+            y = 0;
+            z = 0;
+
+            if( p.periodic )
+            {
+                t = t-(int)Math.Floor(t);
+            }
+            x = spline1d.spline1dcalc(p.x, t, _params);
+            y = spline1d.spline1dcalc(p.y, t, _params);
+            z = spline1d.spline1dcalc(p.z, t, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            if( ((double)(x)!=(double)(0) || (double)(y)!=(double)(0)) || (double)(z)!=(double)(0) )
+            {
+                v = apserv.safepythag3(x, y, z, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            spline1d.spline1ddiff(p.y, t, ref y, ref dy, ref d2s, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            spline1d.spline1ddiff(p.y, t, ref y, ref dy, ref d2s, _params);
+            spline1d.spline1ddiff(p.z, t, ref z, ref dz, ref d2s, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            spline1d.spline1ddiff(p.y, t, ref y, ref dy, ref d2y, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            spline1d.spline1ddiff(p.y, t, ref y, ref dy, ref d2y, _params);
+            spline1d.spline1ddiff(p.z, t, ref z, ref dz, ref d2z, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            while( autogk.autogkiteration(state, _params) )
+            {
+                spline1d.spline1ddiff(p.x, state.x, ref sx, ref dsx, ref d2sx, _params);
+                spline1d.spline1ddiff(p.y, state.x, ref sy, ref dsy, ref d2sy, _params);
+                state.f = apserv.safepythag2(dsx, dsy, _params);
+            }
+            autogk.autogkresults(state, ref result, rep, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            while( autogk.autogkiteration(state, _params) )
+            {
+                spline1d.spline1ddiff(p.x, state.x, ref sx, ref dsx, ref d2sx, _params);
+                spline1d.spline1ddiff(p.y, state.x, ref sy, ref dsy, ref d2sy, _params);
+                spline1d.spline1ddiff(p.z, state.x, ref sz, ref dsz, ref d2sz, _params);
+                state.f = apserv.safepythag3(dsx, dsy, dsz, _params);
+            }
+            autogk.autogkresults(state, ref result, rep, _params);
+            alglib.ap.assert(rep.terminationtype>0, "PSpline3ArcLength: internal error!");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This  subroutine fits piecewise linear curve to points with Ramer-Douglas-
+        Peucker algorithm. This  function  performs PARAMETRIC fit, i.e. it can be
+        used to fit curves like circles.
+
+        On  input  it  accepts dataset which describes parametric multidimensional
+        curve X(t), with X being vector, and t taking values in [0,N), where N  is
+        a number of points in dataset. As result, it returns reduced  dataset  X2,
+        which can be used to build  parametric  curve  X2(t),  which  approximates
+        X(t) with desired precision (or has specified number of sections).
+
+
+        INPUT PARAMETERS:
+            X       -   array of multidimensional points:
+                        * at least N elements, leading N elements are used if more
+                          than N elements were specified
+                        * order of points is IMPORTANT because  it  is  parametric
+                          fit
+                        * each row of array is one point which has D coordinates
+            N       -   number of elements in X
+            D       -   number of dimensions (elements per row of X)
+            StopM   -   stopping condition - desired number of sections:
+                        * at most M sections are generated by this function
+                        * less than M sections can be generated if we have N<M
+                          (or some X are non-distinct).
+                        * zero StopM means that algorithm does not stop after
+                          achieving some pre-specified section count
+            StopEps -   stopping condition - desired precision:
+                        * algorithm stops after error in each section is at most Eps
+                        * zero Eps means that algorithm does not stop after
+                          achieving some pre-specified precision
+
+        OUTPUT PARAMETERS:
+            X2      -   array of corner points for piecewise approximation,
+                        has length NSections+1 or zero (for NSections=0).
+            Idx2    -   array of indexes (parameter values):
+                        * has length NSections+1 or zero (for NSections=0).
+                        * each element of Idx2 corresponds to same-numbered
+                          element of X2
+                        * each element of Idx2 is index of  corresponding  element
+                          of X2 at original array X, i.e. I-th  row  of  X2  is
+                          Idx2[I]-th row of X.
+                        * elements of Idx2 can be treated as parameter values
+                          which should be used when building new parametric curve
+                        * Idx2[0]=0, Idx2[NSections]=N-1
+            NSections-  number of sections found by algorithm, NSections<=M,
+                        NSections can be zero for degenerate datasets
+                        (N<=1 or all X[] are non-distinct).
+
+        NOTE: algorithm stops after:
+              a) dividing curve into StopM sections 
+              b) achieving required precision StopEps
+              c) dividing curve into N-1 sections
+              If both StopM and StopEps are non-zero, algorithm is stopped by  the
+              FIRST criterion which is satisfied. In case both StopM  and  StopEps
+              are zero, algorithm stops because of (c).
+                        
+          -- ALGLIB --
+             Copyright 02.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void parametricrdpfixed(double[,] x,
+            int n,
+            int d,
+            int stopm,
+            double stopeps,
+            ref double[,] x2,
+            ref int[] idx2,
+            ref int nsections,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            bool allsame = new bool();
+            int k0 = 0;
+            int k1 = 0;
+            int k2 = 0;
+            double e0 = 0;
+            double e1 = 0;
+            int idx0 = 0;
+            int idx1 = 0;
+            int worstidx = 0;
+            double worsterror = 0;
+            double[,] sections = new double[0,0];
+            double[] heaperrors = new double[0];
+            int[] heaptags = new int[0];
+            double[] buf0 = new double[0];
+            double[] buf1 = new double[0];
+
+            x2 = new double[0,0];
+            idx2 = new int[0];
+            nsections = 0;
+
+            alglib.ap.assert(n>=0, "LSTFitPiecewiseLinearParametricRDP: N<0");
+            alglib.ap.assert(d>=1, "LSTFitPiecewiseLinearParametricRDP: D<=0");
+            alglib.ap.assert(stopm>=0, "LSTFitPiecewiseLinearParametricRDP: StopM<1");
+            alglib.ap.assert(math.isfinite(stopeps) && (double)(stopeps)>=(double)(0), "LSTFitPiecewiseLinearParametricRDP: StopEps<0 or is infinite");
+            alglib.ap.assert(alglib.ap.rows(x)>=n, "LSTFitPiecewiseLinearParametricRDP: Rows(X)<N");
+            alglib.ap.assert(alglib.ap.cols(x)>=d, "LSTFitPiecewiseLinearParametricRDP: Cols(X)<D");
+            alglib.ap.assert(apserv.apservisfinitematrix(x, n, d, _params), "LSTFitPiecewiseLinearParametricRDP: X contains infinite/NAN values");
+            
+            //
+            // Handle degenerate cases
+            //
+            if( n<=1 )
+            {
+                nsections = 0;
+                return;
+            }
+            allsame = true;
+            for(i=1; i<=n-1; i++)
+            {
+                for(j=0; j<=d-1; j++)
+                {
+                    allsame = allsame && (double)(x[i,j])==(double)(x[0,j]);
+                }
+            }
+            if( allsame )
+            {
+                nsections = 0;
+                return;
+            }
+            
+            //
+            // Prepare first section
+            //
+            rdpanalyzesectionpar(x, 0, n-1, d, ref worstidx, ref worsterror, _params);
+            sections = new double[n, 4];
+            heaperrors = new double[n];
+            heaptags = new int[n];
+            nsections = 1;
+            sections[0,0] = 0;
+            sections[0,1] = n-1;
+            sections[0,2] = worstidx;
+            sections[0,3] = worsterror;
+            heaperrors[0] = worsterror;
+            heaptags[0] = 0;
+            alglib.ap.assert((double)(sections[0,1])==(double)(n-1), "RDP algorithm: integrity check failed");
+            
+            //
+            // Main loop.
+            // Repeatedly find section with worst error and divide it.
+            // Terminate after M-th section, or because of other reasons (see loop internals).
+            //
+            while( true )
+            {
+                
+                //
+                // Break loop if one of the stopping conditions was met.
+                // Store index of worst section to K.
+                //
+                if( (double)(heaperrors[0])==(double)(0) )
+                {
+                    break;
+                }
+                if( (double)(stopeps)>(double)(0) && (double)(heaperrors[0])<=(double)(stopeps) )
+                {
+                    break;
+                }
+                if( stopm>0 && nsections>=stopm )
+                {
+                    break;
+                }
+                k = heaptags[0];
+                
+                //
+                // K-th section is divided in two:
+                // * first  one spans interval from X[Sections[K,0]] to X[Sections[K,2]]
+                // * second one spans interval from X[Sections[K,2]] to X[Sections[K,1]]
+                //
+                // First section is stored at K-th position, second one is appended to the table.
+                // Then we update heap which stores pairs of (error,section_index)
+                //
+                k0 = (int)Math.Round(sections[k,0]);
+                k1 = (int)Math.Round(sections[k,1]);
+                k2 = (int)Math.Round(sections[k,2]);
+                rdpanalyzesectionpar(x, k0, k2, d, ref idx0, ref e0, _params);
+                rdpanalyzesectionpar(x, k2, k1, d, ref idx1, ref e1, _params);
+                sections[k,0] = k0;
+                sections[k,1] = k2;
+                sections[k,2] = idx0;
+                sections[k,3] = e0;
+                tsort.tagheapreplacetopi(ref heaperrors, ref heaptags, nsections, e0, k, _params);
+                sections[nsections,0] = k2;
+                sections[nsections,1] = k1;
+                sections[nsections,2] = idx1;
+                sections[nsections,3] = e1;
+                tsort.tagheappushi(ref heaperrors, ref heaptags, ref nsections, e1, nsections, _params);
+            }
+            
+            //
+            // Convert from sections to indexes
+            //
+            buf0 = new double[nsections+1];
+            for(i=0; i<=nsections-1; i++)
+            {
+                buf0[i] = (int)Math.Round(sections[i,0]);
+            }
+            buf0[nsections] = n-1;
+            tsort.tagsortfast(ref buf0, ref buf1, nsections+1, _params);
+            idx2 = new int[nsections+1];
+            for(i=0; i<=nsections; i++)
+            {
+                idx2[i] = (int)Math.Round(buf0[i]);
+            }
+            alglib.ap.assert(idx2[0]==0, "RDP algorithm: integrity check failed");
+            alglib.ap.assert(idx2[nsections]==n-1, "RDP algorithm: integrity check failed");
+            
+            //
+            // Output sections:
+            // * first NSection elements of X2/Y2 are filled by x/y at left boundaries of sections
+            // * last element of X2/Y2 is filled by right boundary of rightmost section
+            // * X2/Y2 is sorted by ascending of X2
+            //
+            x2 = new double[nsections+1, d];
+            for(i=0; i<=nsections; i++)
+            {
+                for(j=0; j<=d-1; j++)
+                {
+                    x2[i,j] = x[idx2[i],j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Builds non-periodic parameterization for 2-dimensional spline
+        *************************************************************************/
+        private static void pspline2par(double[,] xy,
+            int n,
+            int pt,
+            ref double[] p,
+            alglib.xparams _params)
+        {
+            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], _params);
+                }
+            }
+            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], _params));
+                }
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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], _params);
+                }
+            }
+            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], _params));
+                }
+            }
+            v = 1/p[n-1];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                p[i_] = v*p[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This function analyzes section of curve for processing by RDP algorithm:
+        given set of points X,Y with indexes [I0,I1] it returns point with
+        worst deviation from linear model (PARAMETRIC version which sees curve
+        as X(t) with vector X).
+
+        Input parameters:
+            XY          -   array
+            I0,I1       -   interval (boundaries included) to process
+            D           -   number of dimensions
+            
+        OUTPUT PARAMETERS:
+            WorstIdx    -   index of worst point
+            WorstError  -   error at worst point
+            
+        NOTE: this function guarantees that it returns exactly zero for a section
+              with less than 3 points.
+
+          -- ALGLIB PROJECT --
+             Copyright 02.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rdpanalyzesectionpar(double[,] xy,
+            int i0,
+            int i1,
+            int d,
+            ref int worstidx,
+            ref double worsterror,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double d2 = 0;
+            double ts = 0;
+            double vv = 0;
+
+            worstidx = 0;
+            worsterror = 0;
+
+            
+            //
+            // Quick exit for 0, 1, 2 points
+            //
+            if( i1-i0+1<3 )
+            {
+                worstidx = i0;
+                worsterror = 0.0;
+                return;
+            }
+            
+            //
+            // Estimate D2 - squared distance between XY[I1] and XY[I0].
+            // In case D2=0 handle it as special case.
+            //
+            d2 = 0.0;
+            for(j=0; j<=d-1; j++)
+            {
+                d2 = d2+math.sqr(xy[i1,j]-xy[i0,j]);
+            }
+            if( (double)(d2)==(double)(0) )
+            {
+                
+                //
+                // First and last points are equal, interval evaluation is
+                // trivial - we just calculate distance from all points to
+                // the first/last one.
+                //
+                worstidx = i0;
+                worsterror = 0.0;
+                for(i=i0+1; i<=i1-1; i++)
+                {
+                    vv = 0.0;
+                    for(j=0; j<=d-1; j++)
+                    {
+                        v = xy[i,j]-xy[i0,j];
+                        vv = vv+v*v;
+                    }
+                    vv = Math.Sqrt(vv);
+                    if( (double)(vv)>(double)(worsterror) )
+                    {
+                        worsterror = vv;
+                        worstidx = i;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // General case
+            //
+            // Current section of curve is modeled as x(t) = d*t+c, where
+            //     d = XY[I1]-XY[I0]
+            //     c = XY[I0]
+            //     t is in [0,1]
+            //
+            worstidx = i0;
+            worsterror = 0.0;
+            for(i=i0+1; i<=i1-1; i++)
+            {
+                
+                //
+                // Determine t_s - parameter value for projected point.
+                //
+                ts = (double)(i-i0)/(double)(i1-i0);
+                
+                //
+                // Estimate error norm
+                //
+                vv = 0.0;
+                for(j=0; j<=d-1; j++)
+                {
+                    v = (xy[i1,j]-xy[i0,j])*ts-(xy[i,j]-xy[i0,j]);
+                    vv = vv+math.sqr(v);
+                }
+                vv = Math.Sqrt(vv);
+                if( (double)(vv)>(double)(worsterror) )
+                {
+                    worsterror = vv;
+                    worstidx = i;
+                }
+            }
+        }
+
+
+    }
+    public class spline3d
+    {
+        /*************************************************************************
+        3-dimensional spline inteprolant
+        *************************************************************************/
+        public class spline3dinterpolant : apobject
+        {
+            public int k;
+            public int stype;
+            public int n;
+            public int m;
+            public int l;
+            public int d;
+            public double[] x;
+            public double[] y;
+            public double[] z;
+            public double[] f;
+            public spline3dinterpolant()
+            {
+                init();
+            }
+            public override void init()
+            {
+                x = new double[0];
+                y = new double[0];
+                z = new double[0];
+                f = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                spline3dinterpolant _result = new spline3dinterpolant();
+                _result.k = k;
+                _result.stype = stype;
+                _result.n = n;
+                _result.m = m;
+                _result.l = l;
+                _result.d = d;
+                _result.x = (double[])x.Clone();
+                _result.y = (double[])y.Clone();
+                _result.z = (double[])z.Clone();
+                _result.f = (double[])f.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        This subroutine calculates the value of the trilinear or tricubic spline at
+        the given point (X,Y,Z).
+
+        INPUT PARAMETERS:
+            C   -   coefficients table.
+                    Built by BuildBilinearSpline or BuildBicubicSpline.
+            X, Y,
+            Z   -   point
+
+        Result:
+            S(x,y,z)
+
+          -- ALGLIB PROJECT --
+             Copyright 26.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double spline3dcalc(spline3dinterpolant c,
+            double x,
+            double y,
+            double z,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double v = 0;
+            double vx = 0;
+            double vy = 0;
+            double vxy = 0;
+
+            alglib.ap.assert(c.stype==-1 || c.stype==-3, "Spline3DCalc: incorrect C (incorrect parameter C.SType)");
+            alglib.ap.assert((math.isfinite(x) && math.isfinite(y)) && math.isfinite(z), "Spline3DCalc: X=NaN/Infinite, Y=NaN/Infinite or Z=NaN/Infinite");
+            if( c.d!=1 )
+            {
+                result = 0;
+                return result;
+            }
+            spline3ddiff(c, x, y, z, ref v, ref vx, ref vy, ref vxy, _params);
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine performs linear transformation of the spline argument.
+
+        INPUT PARAMETERS:
+            C       -   spline interpolant
+            AX, BX  -   transformation coefficients: x = A*u + B
+            AY, BY  -   transformation coefficients: y = A*v + B
+            AZ, BZ  -   transformation coefficients: z = A*w + B
+            
+        OUTPUT PARAMETERS:
+            C   -   transformed spline
+
+          -- ALGLIB PROJECT --
+             Copyright 26.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline3dlintransxyz(spline3dinterpolant c,
+            double ax,
+            double bx,
+            double ay,
+            double by,
+            double az,
+            double bz,
+            alglib.xparams _params)
+        {
+            double[] x = new double[0];
+            double[] y = new double[0];
+            double[] z = new double[0];
+            double[] f = new double[0];
+            double[] v = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int di = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(c.stype==-3 || c.stype==-1, "Spline3DLinTransXYZ: incorrect C (incorrect parameter C.SType)");
+            x = new double[c.n];
+            y = new double[c.m];
+            z = new double[c.l];
+            f = new double[c.m*c.n*c.l*c.d];
+            for(j=0; j<=c.n-1; j++)
+            {
+                x[j] = c.x[j];
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                y[i] = c.y[i];
+            }
+            for(i=0; i<=c.l-1; i++)
+            {
+                z[i] = c.z[i];
+            }
+            
+            //
+            // Handle different combinations of zero/nonzero AX/AY/AZ
+            //
+            if( ((double)(ax)!=(double)(0) && (double)(ay)!=(double)(0)) && (double)(az)!=(double)(0) )
+            {
+                for(i_=0; i_<=c.m*c.n*c.l*c.d-1;i_++)
+                {
+                    f[i_] = c.f[i_];
+                }
+            }
+            if( ((double)(ax)==(double)(0) && (double)(ay)!=(double)(0)) && (double)(az)!=(double)(0) )
+            {
+                for(i=0; i<=c.m-1; i++)
+                {
+                    for(j=0; j<=c.l-1; j++)
+                    {
+                        spline3dcalcv(c, bx, y[i], z[j], ref v, _params);
+                        for(k=0; k<=c.n-1; k++)
+                        {
+                            for(di=0; di<=c.d-1; di++)
+                            {
+                                f[c.d*(c.n*(c.m*j+i)+k)+di] = v[di];
+                            }
+                        }
+                    }
+                }
+                ax = 1;
+                bx = 0;
+            }
+            if( ((double)(ax)!=(double)(0) && (double)(ay)==(double)(0)) && (double)(az)!=(double)(0) )
+            {
+                for(i=0; i<=c.n-1; i++)
+                {
+                    for(j=0; j<=c.l-1; j++)
+                    {
+                        spline3dcalcv(c, x[i], by, z[j], ref v, _params);
+                        for(k=0; k<=c.m-1; k++)
+                        {
+                            for(di=0; di<=c.d-1; di++)
+                            {
+                                f[c.d*(c.n*(c.m*j+k)+i)+di] = v[di];
+                            }
+                        }
+                    }
+                }
+                ay = 1;
+                by = 0;
+            }
+            if( ((double)(ax)!=(double)(0) && (double)(ay)!=(double)(0)) && (double)(az)==(double)(0) )
+            {
+                for(i=0; i<=c.n-1; i++)
+                {
+                    for(j=0; j<=c.m-1; j++)
+                    {
+                        spline3dcalcv(c, x[i], y[j], bz, ref v, _params);
+                        for(k=0; k<=c.l-1; k++)
+                        {
+                            for(di=0; di<=c.d-1; di++)
+                            {
+                                f[c.d*(c.n*(c.m*k+j)+i)+di] = v[di];
+                            }
+                        }
+                    }
+                }
+                az = 1;
+                bz = 0;
+            }
+            if( ((double)(ax)==(double)(0) && (double)(ay)==(double)(0)) && (double)(az)!=(double)(0) )
+            {
+                for(i=0; i<=c.l-1; i++)
+                {
+                    spline3dcalcv(c, bx, by, z[i], ref v, _params);
+                    for(k=0; k<=c.m-1; k++)
+                    {
+                        for(j=0; j<=c.n-1; j++)
+                        {
+                            for(di=0; di<=c.d-1; di++)
+                            {
+                                f[c.d*(c.n*(c.m*i+k)+j)+di] = v[di];
+                            }
+                        }
+                    }
+                }
+                ax = 1;
+                bx = 0;
+                ay = 1;
+                by = 0;
+            }
+            if( ((double)(ax)==(double)(0) && (double)(ay)!=(double)(0)) && (double)(az)==(double)(0) )
+            {
+                for(i=0; i<=c.m-1; i++)
+                {
+                    spline3dcalcv(c, bx, y[i], bz, ref v, _params);
+                    for(k=0; k<=c.l-1; k++)
+                    {
+                        for(j=0; j<=c.n-1; j++)
+                        {
+                            for(di=0; di<=c.d-1; di++)
+                            {
+                                f[c.d*(c.n*(c.m*k+i)+j)+di] = v[di];
+                            }
+                        }
+                    }
+                }
+                ax = 1;
+                bx = 0;
+                az = 1;
+                bz = 0;
+            }
+            if( ((double)(ax)!=(double)(0) && (double)(ay)==(double)(0)) && (double)(az)==(double)(0) )
+            {
+                for(i=0; i<=c.n-1; i++)
+                {
+                    spline3dcalcv(c, x[i], by, bz, ref v, _params);
+                    for(k=0; k<=c.l-1; k++)
+                    {
+                        for(j=0; j<=c.m-1; j++)
+                        {
+                            for(di=0; di<=c.d-1; di++)
+                            {
+                                f[c.d*(c.n*(c.m*k+j)+i)+di] = v[di];
+                            }
+                        }
+                    }
+                }
+                ay = 1;
+                by = 0;
+                az = 1;
+                bz = 0;
+            }
+            if( ((double)(ax)==(double)(0) && (double)(ay)==(double)(0)) && (double)(az)==(double)(0) )
+            {
+                spline3dcalcv(c, bx, by, bz, ref v, _params);
+                for(k=0; k<=c.l-1; k++)
+                {
+                    for(j=0; j<=c.m-1; j++)
+                    {
+                        for(i=0; i<=c.n-1; i++)
+                        {
+                            for(di=0; di<=c.d-1; di++)
+                            {
+                                f[c.d*(c.n*(c.m*k+j)+i)+di] = v[di];
+                            }
+                        }
+                    }
+                }
+                ax = 1;
+                bx = 0;
+                ay = 1;
+                by = 0;
+                az = 1;
+                bz = 0;
+            }
+            
+            //
+            // General case: AX<>0, AY<>0, AZ<>0
+            // Unpack, scale and pack again.
+            //
+            for(i=0; i<=c.n-1; i++)
+            {
+                x[i] = (x[i]-bx)/ax;
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                y[i] = (y[i]-by)/ay;
+            }
+            for(i=0; i<=c.l-1; i++)
+            {
+                z[i] = (z[i]-bz)/az;
+            }
+            if( c.stype==-1 )
+            {
+                spline3dbuildtrilinearv(x, c.n, y, c.m, z, c.l, f, c.d, c, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine performs linear transformation of the spline.
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+            A, B-   transformation coefficients: S2(x,y) = A*S(x,y,z) + B
+            
+        OUTPUT PARAMETERS:
+            C   -   transformed spline
+
+          -- ALGLIB PROJECT --
+             Copyright 26.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline3dlintransf(spline3dinterpolant c,
+            double a,
+            double b,
+            alglib.xparams _params)
+        {
+            double[] x = new double[0];
+            double[] y = new double[0];
+            double[] z = new double[0];
+            double[] f = new double[0];
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(c.stype==-3 || c.stype==-1, "Spline3DLinTransF: incorrect C (incorrect parameter C.SType)");
+            x = new double[c.n];
+            y = new double[c.m];
+            z = new double[c.l];
+            f = new double[c.m*c.n*c.l*c.d];
+            for(j=0; j<=c.n-1; j++)
+            {
+                x[j] = c.x[j];
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                y[i] = c.y[i];
+            }
+            for(i=0; i<=c.l-1; i++)
+            {
+                z[i] = c.z[i];
+            }
+            for(i=0; i<=c.m*c.n*c.l*c.d-1; i++)
+            {
+                f[i] = a*c.f[i]+b;
+            }
+            if( c.stype==-1 )
+            {
+                spline3dbuildtrilinearv(x, c.n, y, c.m, z, c.l, f, c.d, c, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine makes the copy of the spline model.
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant
+
+        OUTPUT PARAMETERS:
+            CC  -   spline copy
+
+          -- ALGLIB PROJECT --
+             Copyright 26.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline3dcopy(spline3dinterpolant c,
+            spline3dinterpolant cc,
+            alglib.xparams _params)
+        {
+            int tblsize = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(c.k==1 || c.k==3, "Spline3DCopy: incorrect C (incorrect parameter C.K)");
+            cc.k = c.k;
+            cc.n = c.n;
+            cc.m = c.m;
+            cc.l = c.l;
+            cc.d = c.d;
+            tblsize = c.n*c.m*c.l*c.d;
+            cc.stype = c.stype;
+            cc.x = new double[cc.n];
+            cc.y = new double[cc.m];
+            cc.z = new double[cc.l];
+            cc.f = new double[tblsize];
+            for(i_=0; i_<=cc.n-1;i_++)
+            {
+                cc.x[i_] = c.x[i_];
+            }
+            for(i_=0; i_<=cc.m-1;i_++)
+            {
+                cc.y[i_] = c.y[i_];
+            }
+            for(i_=0; i_<=cc.l-1;i_++)
+            {
+                cc.z[i_] = c.z[i_];
+            }
+            for(i_=0; i_<=tblsize-1;i_++)
+            {
+                cc.f[i_] = c.f[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        Trilinear spline resampling
+
+        INPUT PARAMETERS:
+            A           -   array[0..OldXCount*OldYCount*OldZCount-1], function
+                            values at the old grid, :
+                                A[0]        x=0,y=0,z=0
+                                A[1]        x=1,y=0,z=0
+                                A[..]       ...
+                                A[..]       x=oldxcount-1,y=0,z=0
+                                A[..]       x=0,y=1,z=0
+                                A[..]       ...
+                                ...
+            OldZCount   -   old Z-count, OldZCount>1
+            OldYCount   -   old Y-count, OldYCount>1
+            OldXCount   -   old X-count, OldXCount>1
+            NewZCount   -   new Z-count, NewZCount>1
+            NewYCount   -   new Y-count, NewYCount>1
+            NewXCount   -   new X-count, NewXCount>1
+
+        OUTPUT PARAMETERS:
+            B           -   array[0..NewXCount*NewYCount*NewZCount-1], function
+                            values at the new grid:
+                                B[0]        x=0,y=0,z=0
+                                B[1]        x=1,y=0,z=0
+                                B[..]       ...
+                                B[..]       x=newxcount-1,y=0,z=0
+                                B[..]       x=0,y=1,z=0
+                                B[..]       ...
+                                ...
+
+          -- ALGLIB routine --
+             26.04.2012
+             Copyright by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline3dresampletrilinear(double[] a,
+            int oldzcount,
+            int oldycount,
+            int oldxcount,
+            int newzcount,
+            int newycount,
+            int newxcount,
+            ref double[] b,
+            alglib.xparams _params)
+        {
+            double xd = 0;
+            double yd = 0;
+            double zd = 0;
+            double c0 = 0;
+            double c1 = 0;
+            double c2 = 0;
+            double c3 = 0;
+            int ix = 0;
+            int iy = 0;
+            int iz = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+
+            b = new double[0];
+
+            alglib.ap.assert((oldycount>1 && oldzcount>1) && oldxcount>1, "Spline3DResampleTrilinear: length/width/height less than 1");
+            alglib.ap.assert((newycount>1 && newzcount>1) && newxcount>1, "Spline3DResampleTrilinear: length/width/height less than 1");
+            alglib.ap.assert(alglib.ap.len(a)>=oldycount*oldzcount*oldxcount, "Spline3DResampleTrilinear: length/width/height less than 1");
+            b = new double[newxcount*newycount*newzcount];
+            for(i=0; i<=newxcount-1; i++)
+            {
+                for(j=0; j<=newycount-1; j++)
+                {
+                    for(k=0; k<=newzcount-1; k++)
+                    {
+                        ix = i*(oldxcount-1)/(newxcount-1);
+                        if( ix==oldxcount-1 )
+                        {
+                            ix = oldxcount-2;
+                        }
+                        xd = (double)(i*(oldxcount-1))/(double)(newxcount-1)-ix;
+                        iy = j*(oldycount-1)/(newycount-1);
+                        if( iy==oldycount-1 )
+                        {
+                            iy = oldycount-2;
+                        }
+                        yd = (double)(j*(oldycount-1))/(double)(newycount-1)-iy;
+                        iz = k*(oldzcount-1)/(newzcount-1);
+                        if( iz==oldzcount-1 )
+                        {
+                            iz = oldzcount-2;
+                        }
+                        zd = (double)(k*(oldzcount-1))/(double)(newzcount-1)-iz;
+                        c0 = a[oldxcount*(oldycount*iz+iy)+ix]*(1-xd)+a[oldxcount*(oldycount*iz+iy)+(ix+1)]*xd;
+                        c1 = a[oldxcount*(oldycount*iz+(iy+1))+ix]*(1-xd)+a[oldxcount*(oldycount*iz+(iy+1))+(ix+1)]*xd;
+                        c2 = a[oldxcount*(oldycount*(iz+1)+iy)+ix]*(1-xd)+a[oldxcount*(oldycount*(iz+1)+iy)+(ix+1)]*xd;
+                        c3 = a[oldxcount*(oldycount*(iz+1)+(iy+1))+ix]*(1-xd)+a[oldxcount*(oldycount*(iz+1)+(iy+1))+(ix+1)]*xd;
+                        c0 = c0*(1-yd)+c1*yd;
+                        c1 = c2*(1-yd)+c3*yd;
+                        b[newxcount*(newycount*k+j)+i] = c0*(1-zd)+c1*zd;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine builds trilinear vector-valued spline.
+
+        INPUT PARAMETERS:
+            X   -   spline abscissas,  array[0..N-1]
+            Y   -   spline ordinates,  array[0..M-1]
+            Z   -   spline applicates, array[0..L-1] 
+            F   -   function values, array[0..M*N*L*D-1]:
+                    * first D elements store D values at (X[0],Y[0],Z[0])
+                    * next D elements store D values at (X[1],Y[0],Z[0])
+                    * next D elements store D values at (X[2],Y[0],Z[0])
+                    * ...
+                    * next D elements store D values at (X[0],Y[1],Z[0])
+                    * next D elements store D values at (X[1],Y[1],Z[0])
+                    * next D elements store D values at (X[2],Y[1],Z[0])
+                    * ...
+                    * next D elements store D values at (X[0],Y[0],Z[1])
+                    * next D elements store D values at (X[1],Y[0],Z[1])
+                    * next D elements store D values at (X[2],Y[0],Z[1])
+                    * ...
+                    * general form - D function values at (X[i],Y[j]) are stored
+                      at F[D*(N*(M*K+J)+I)...D*(N*(M*K+J)+I)+D-1].
+            M,N,
+            L   -   grid size, M>=2, N>=2, L>=2
+            D   -   vector dimension, D>=1
+
+        OUTPUT PARAMETERS:
+            C   -   spline interpolant
+
+          -- ALGLIB PROJECT --
+             Copyright 26.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline3dbuildtrilinearv(double[] x,
+            int n,
+            double[] y,
+            int m,
+            double[] z,
+            int l,
+            double[] f,
+            int d,
+            spline3dinterpolant c,
+            alglib.xparams _params)
+        {
+            double t = 0;
+            int tblsize = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int i0 = 0;
+            int j0 = 0;
+
+            alglib.ap.assert(m>=2, "Spline3DBuildTrilinearV: M<2");
+            alglib.ap.assert(n>=2, "Spline3DBuildTrilinearV: N<2");
+            alglib.ap.assert(l>=2, "Spline3DBuildTrilinearV: L<2");
+            alglib.ap.assert(d>=1, "Spline3DBuildTrilinearV: D<1");
+            alglib.ap.assert((alglib.ap.len(x)>=n && alglib.ap.len(y)>=m) && alglib.ap.len(z)>=l, "Spline3DBuildTrilinearV: length of X, Y or Z is too short (Length(X/Y/Z)<N/M/L)");
+            alglib.ap.assert((apserv.isfinitevector(x, n, _params) && apserv.isfinitevector(y, m, _params)) && apserv.isfinitevector(z, l, _params), "Spline3DBuildTrilinearV: X, Y or Z contains NaN or Infinite value");
+            tblsize = n*m*l*d;
+            alglib.ap.assert(alglib.ap.len(f)>=tblsize, "Spline3DBuildTrilinearV: length of F is too short (Length(F)<N*M*L*D)");
+            alglib.ap.assert(apserv.isfinitevector(f, tblsize, _params), "Spline3DBuildTrilinearV: F contains NaN or Infinite value");
+            
+            //
+            // Fill interpolant
+            //
+            c.k = 1;
+            c.n = n;
+            c.m = m;
+            c.l = l;
+            c.d = d;
+            c.stype = -1;
+            c.x = new double[c.n];
+            c.y = new double[c.m];
+            c.z = new double[c.l];
+            c.f = new double[tblsize];
+            for(i=0; i<=c.n-1; i++)
+            {
+                c.x[i] = x[i];
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                c.y[i] = y[i];
+            }
+            for(i=0; i<=c.l-1; i++)
+            {
+                c.z[i] = z[i];
+            }
+            for(i=0; i<=tblsize-1; i++)
+            {
+                c.f[i] = f[i];
+            }
+            
+            //
+            // Sort points:
+            //  * sort x;
+            //  * sort y;
+            //  * sort z.
+            //
+            for(j=0; j<=c.n-1; j++)
+            {
+                k = j;
+                for(i=j+1; i<=c.n-1; i++)
+                {
+                    if( (double)(c.x[i])<(double)(c.x[k]) )
+                    {
+                        k = i;
+                    }
+                }
+                if( k!=j )
+                {
+                    for(i=0; i<=c.m-1; i++)
+                    {
+                        for(j0=0; j0<=c.l-1; j0++)
+                        {
+                            for(i0=0; i0<=c.d-1; i0++)
+                            {
+                                t = c.f[c.d*(c.n*(c.m*j0+i)+j)+i0];
+                                c.f[c.d*(c.n*(c.m*j0+i)+j)+i0] = c.f[c.d*(c.n*(c.m*j0+i)+k)+i0];
+                                c.f[c.d*(c.n*(c.m*j0+i)+k)+i0] = t;
+                            }
+                        }
+                    }
+                    t = c.x[j];
+                    c.x[j] = c.x[k];
+                    c.x[k] = t;
+                }
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                k = i;
+                for(j=i+1; j<=c.m-1; j++)
+                {
+                    if( (double)(c.y[j])<(double)(c.y[k]) )
+                    {
+                        k = j;
+                    }
+                }
+                if( k!=i )
+                {
+                    for(j=0; j<=c.n-1; j++)
+                    {
+                        for(j0=0; j0<=c.l-1; j0++)
+                        {
+                            for(i0=0; i0<=c.d-1; i0++)
+                            {
+                                t = c.f[c.d*(c.n*(c.m*j0+i)+j)+i0];
+                                c.f[c.d*(c.n*(c.m*j0+i)+j)+i0] = c.f[c.d*(c.n*(c.m*j0+k)+j)+i0];
+                                c.f[c.d*(c.n*(c.m*j0+k)+j)+i0] = t;
+                            }
+                        }
+                    }
+                    t = c.y[i];
+                    c.y[i] = c.y[k];
+                    c.y[k] = t;
+                }
+            }
+            for(k=0; k<=c.l-1; k++)
+            {
+                i = k;
+                for(j=i+1; j<=c.l-1; j++)
+                {
+                    if( (double)(c.z[j])<(double)(c.z[i]) )
+                    {
+                        i = j;
+                    }
+                }
+                if( i!=k )
+                {
+                    for(j=0; j<=c.m-1; j++)
+                    {
+                        for(j0=0; j0<=c.n-1; j0++)
+                        {
+                            for(i0=0; i0<=c.d-1; i0++)
+                            {
+                                t = c.f[c.d*(c.n*(c.m*k+j)+j0)+i0];
+                                c.f[c.d*(c.n*(c.m*k+j)+j0)+i0] = c.f[c.d*(c.n*(c.m*i+j)+j0)+i0];
+                                c.f[c.d*(c.n*(c.m*i+j)+j0)+i0] = t;
+                            }
+                        }
+                    }
+                    t = c.z[k];
+                    c.z[k] = c.z[i];
+                    c.z[i] = t;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates bilinear or bicubic vector-valued spline at the
+        given point (X,Y,Z).
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+            X, Y,
+            Z   -   point
+            F   -   output buffer, possibly preallocated array. In case array size
+                    is large enough to store result, it is not reallocated.  Array
+                    which is too short will be reallocated
+
+        OUTPUT PARAMETERS:
+            F   -   array[D] (or larger) which stores function values
+
+          -- ALGLIB PROJECT --
+             Copyright 26.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline3dcalcvbuf(spline3dinterpolant c,
+            double x,
+            double y,
+            double z,
+            ref double[] f,
+            alglib.xparams _params)
+        {
+            double xd = 0;
+            double yd = 0;
+            double zd = 0;
+            double c0 = 0;
+            double c1 = 0;
+            double c2 = 0;
+            double c3 = 0;
+            int ix = 0;
+            int iy = 0;
+            int iz = 0;
+            int l = 0;
+            int r = 0;
+            int h = 0;
+            int i = 0;
+
+            alglib.ap.assert(c.stype==-1 || c.stype==-3, "Spline3DCalcVBuf: incorrect C (incorrect parameter C.SType)");
+            alglib.ap.assert((math.isfinite(x) && math.isfinite(y)) && math.isfinite(z), "Spline3DCalcVBuf: X, Y or Z contains NaN/Infinite");
+            apserv.rvectorsetlengthatleast(ref f, c.d, _params);
+            
+            //
+            // Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
+            //
+            l = 0;
+            r = c.n-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.x[h])>=(double)(x) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            ix = l;
+            
+            //
+            // Binary search in the [ y[0], ..., y[n-2] ] (y[n-1] is not included)
+            //
+            l = 0;
+            r = c.m-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.y[h])>=(double)(y) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            iy = l;
+            
+            //
+            // Binary search in the [ z[0], ..., z[n-2] ] (z[n-1] is not included)
+            //
+            l = 0;
+            r = c.l-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.z[h])>=(double)(z) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            iz = l;
+            xd = (x-c.x[ix])/(c.x[ix+1]-c.x[ix]);
+            yd = (y-c.y[iy])/(c.y[iy+1]-c.y[iy]);
+            zd = (z-c.z[iz])/(c.z[iz+1]-c.z[iz]);
+            for(i=0; i<=c.d-1; i++)
+            {
+                
+                //
+                // Trilinear interpolation
+                //
+                if( c.stype==-1 )
+                {
+                    c0 = c.f[c.d*(c.n*(c.m*iz+iy)+ix)+i]*(1-xd)+c.f[c.d*(c.n*(c.m*iz+iy)+(ix+1))+i]*xd;
+                    c1 = c.f[c.d*(c.n*(c.m*iz+(iy+1))+ix)+i]*(1-xd)+c.f[c.d*(c.n*(c.m*iz+(iy+1))+(ix+1))+i]*xd;
+                    c2 = c.f[c.d*(c.n*(c.m*(iz+1)+iy)+ix)+i]*(1-xd)+c.f[c.d*(c.n*(c.m*(iz+1)+iy)+(ix+1))+i]*xd;
+                    c3 = c.f[c.d*(c.n*(c.m*(iz+1)+(iy+1))+ix)+i]*(1-xd)+c.f[c.d*(c.n*(c.m*(iz+1)+(iy+1))+(ix+1))+i]*xd;
+                    c0 = c0*(1-yd)+c1*yd;
+                    c1 = c2*(1-yd)+c3*yd;
+                    f[i] = c0*(1-zd)+c1*zd;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates trilinear or tricubic vector-valued spline at the
+        given point (X,Y,Z).
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+            X, Y,
+            Z   -   point
+
+        OUTPUT PARAMETERS:
+            F   -   array[D] which stores function values.  F is out-parameter and
+                    it  is  reallocated  after  call to this function. In case you
+                    want  to    reuse  previously  allocated  F,   you   may   use
+                    Spline2DCalcVBuf(),  which  reallocates  F only when it is too
+                    small.
+
+          -- ALGLIB PROJECT --
+             Copyright 26.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline3dcalcv(spline3dinterpolant c,
+            double x,
+            double y,
+            double z,
+            ref double[] f,
+            alglib.xparams _params)
+        {
+            f = new double[0];
+
+            alglib.ap.assert(c.stype==-1 || c.stype==-3, "Spline3DCalcV: incorrect C (incorrect parameter C.SType)");
+            alglib.ap.assert((math.isfinite(x) && math.isfinite(y)) && math.isfinite(z), "Spline3DCalcV: X=NaN/Infinite, Y=NaN/Infinite or Z=NaN/Infinite");
+            f = new double[c.d];
+            spline3dcalcvbuf(c, x, y, z, ref f, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine unpacks tri-dimensional spline into the coefficients table
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+
+        Result:
+            N   -   grid size (X)
+            M   -   grid size (Y)
+            L   -   grid size (Z)
+            D   -   number of components
+            SType-  spline type. Currently, only one spline type is supported:
+                    trilinear spline, as indicated by SType=1.
+            Tbl -   spline coefficients: [0..(N-1)*(M-1)*(L-1)*D-1, 0..13].
+                    For T=0..D-1 (component index), I = 0...N-2 (x index),
+                    J=0..M-2 (y index), K=0..L-2 (z index):
+                        Q := T + I*D + J*D*(N-1) + K*D*(N-1)*(M-1),
+                        
+                        Q-th row stores decomposition for T-th component of the
+                        vector-valued function
+                        
+                        Tbl[Q,0] = X[i]
+                        Tbl[Q,1] = X[i+1]
+                        Tbl[Q,2] = Y[j]
+                        Tbl[Q,3] = Y[j+1]
+                        Tbl[Q,4] = Z[k]
+                        Tbl[Q,5] = Z[k+1]
+                        
+                        Tbl[Q,6] = C000
+                        Tbl[Q,7] = C100
+                        Tbl[Q,8] = C010
+                        Tbl[Q,9] = C110
+                        Tbl[Q,10]= C001
+                        Tbl[Q,11]= C101
+                        Tbl[Q,12]= C011
+                        Tbl[Q,13]= C111
+                    On each grid square spline is equals to:
+                        S(x) = SUM(c[i,j,k]*(x^i)*(y^j)*(z^k), i=0..1, j=0..1, k=0..1)
+                        t = x-x[j]
+                        u = y-y[i]
+                        v = z-z[k]
+                    
+                    NOTE: format of Tbl is given for SType=1. Future versions of
+                          ALGLIB can use different formats for different values of
+                          SType.
+
+          -- ALGLIB PROJECT --
+             Copyright 26.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline3dunpackv(spline3dinterpolant c,
+            ref int n,
+            ref int m,
+            ref int l,
+            ref int d,
+            ref int stype,
+            ref double[,] tbl,
+            alglib.xparams _params)
+        {
+            int p = 0;
+            int ci = 0;
+            int cj = 0;
+            int ck = 0;
+            double du = 0;
+            double dv = 0;
+            double dw = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int di = 0;
+            int i0 = 0;
+
+            n = 0;
+            m = 0;
+            l = 0;
+            d = 0;
+            stype = 0;
+            tbl = new double[0,0];
+
+            alglib.ap.assert(c.stype==-1, "Spline3DUnpackV: incorrect C (incorrect parameter C.SType)");
+            n = c.n;
+            m = c.m;
+            l = c.l;
+            d = c.d;
+            stype = Math.Abs(c.stype);
+            tbl = new double[(n-1)*(m-1)*(l-1)*d, 14];
+            
+            //
+            // Fill
+            //
+            for(i=0; i<=n-2; i++)
+            {
+                for(j=0; j<=m-2; j++)
+                {
+                    for(k=0; k<=l-2; k++)
+                    {
+                        for(di=0; di<=d-1; di++)
+                        {
+                            p = d*((n-1)*((m-1)*k+j)+i)+di;
+                            tbl[p,0] = c.x[i];
+                            tbl[p,1] = c.x[i+1];
+                            tbl[p,2] = c.y[j];
+                            tbl[p,3] = c.y[j+1];
+                            tbl[p,4] = c.z[k];
+                            tbl[p,5] = c.z[k+1];
+                            du = 1/(tbl[p,1]-tbl[p,0]);
+                            dv = 1/(tbl[p,3]-tbl[p,2]);
+                            dw = 1/(tbl[p,5]-tbl[p,4]);
+                            
+                            //
+                            // Trilinear interpolation
+                            //
+                            if( c.stype==-1 )
+                            {
+                                for(i0=6; i0<=13; i0++)
+                                {
+                                    tbl[p,i0] = 0;
+                                }
+                                tbl[p,6+2*(2*0+0)+0] = c.f[d*(n*(m*k+j)+i)+di];
+                                tbl[p,6+2*(2*0+0)+1] = c.f[d*(n*(m*k+j)+(i+1))+di]-c.f[d*(n*(m*k+j)+i)+di];
+                                tbl[p,6+2*(2*0+1)+0] = c.f[d*(n*(m*k+(j+1))+i)+di]-c.f[d*(n*(m*k+j)+i)+di];
+                                tbl[p,6+2*(2*0+1)+1] = c.f[d*(n*(m*k+(j+1))+(i+1))+di]-c.f[d*(n*(m*k+(j+1))+i)+di]-c.f[d*(n*(m*k+j)+(i+1))+di]+c.f[d*(n*(m*k+j)+i)+di];
+                                tbl[p,6+2*(2*1+0)+0] = c.f[d*(n*(m*(k+1)+j)+i)+di]-c.f[d*(n*(m*k+j)+i)+di];
+                                tbl[p,6+2*(2*1+0)+1] = c.f[d*(n*(m*(k+1)+j)+(i+1))+di]-c.f[d*(n*(m*(k+1)+j)+i)+di]-c.f[d*(n*(m*k+j)+(i+1))+di]+c.f[d*(n*(m*k+j)+i)+di];
+                                tbl[p,6+2*(2*1+1)+0] = c.f[d*(n*(m*(k+1)+(j+1))+i)+di]-c.f[d*(n*(m*(k+1)+j)+i)+di]-c.f[d*(n*(m*k+(j+1))+i)+di]+c.f[d*(n*(m*k+j)+i)+di];
+                                tbl[p,6+2*(2*1+1)+1] = c.f[d*(n*(m*(k+1)+(j+1))+(i+1))+di]-c.f[d*(n*(m*(k+1)+(j+1))+i)+di]-c.f[d*(n*(m*(k+1)+j)+(i+1))+di]+c.f[d*(n*(m*(k+1)+j)+i)+di]-c.f[d*(n*(m*k+(j+1))+(i+1))+di]+c.f[d*(n*(m*k+(j+1))+i)+di]+c.f[d*(n*(m*k+j)+(i+1))+di]-c.f[d*(n*(m*k+j)+i)+di];
+                            }
+                            
+                            //
+                            // Rescale Cij
+                            //
+                            for(ci=0; ci<=1; ci++)
+                            {
+                                for(cj=0; cj<=1; cj++)
+                                {
+                                    for(ck=0; ck<=1; ck++)
+                                    {
+                                        tbl[p,6+2*(2*ck+cj)+ci] = tbl[p,6+2*(2*ck+cj)+ci]*Math.Pow(du, ci)*Math.Pow(dv, cj)*Math.Pow(dw, ck);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates the value of the trilinear(or tricubic;possible
+        will be later) spline  at the given point X(and its derivatives; possible
+        will be later).
+
+        INPUT PARAMETERS:
+            C       -   spline interpolant.
+            X, Y, Z -   point
+
+        OUTPUT PARAMETERS:
+            F   -   S(x,y,z)
+            FX  -   dS(x,y,z)/dX
+            FY  -   dS(x,y,z)/dY
+            FXY -   d2S(x,y,z)/dXdY
+
+          -- ALGLIB PROJECT --
+             Copyright 26.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        private static void spline3ddiff(spline3dinterpolant c,
+            double x,
+            double y,
+            double z,
+            ref double f,
+            ref double fx,
+            ref double fy,
+            ref double fxy,
+            alglib.xparams _params)
+        {
+            double xd = 0;
+            double yd = 0;
+            double zd = 0;
+            double c0 = 0;
+            double c1 = 0;
+            double c2 = 0;
+            double c3 = 0;
+            int ix = 0;
+            int iy = 0;
+            int iz = 0;
+            int l = 0;
+            int r = 0;
+            int h = 0;
+
+            f = 0;
+            fx = 0;
+            fy = 0;
+            fxy = 0;
+
+            alglib.ap.assert(c.stype==-1 || c.stype==-3, "Spline3DDiff: incorrect C (incorrect parameter C.SType)");
+            alglib.ap.assert(math.isfinite(x) && math.isfinite(y), "Spline3DDiff: X or Y contains NaN or Infinite value");
+            
+            //
+            // Prepare F, dF/dX, dF/dY, d2F/dXdY
+            //
+            f = 0;
+            fx = 0;
+            fy = 0;
+            fxy = 0;
+            if( c.d!=1 )
+            {
+                return;
+            }
+            
+            //
+            // Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
+            //
+            l = 0;
+            r = c.n-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.x[h])>=(double)(x) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            ix = l;
+            
+            //
+            // Binary search in the [ y[0], ..., y[n-2] ] (y[n-1] is not included)
+            //
+            l = 0;
+            r = c.m-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.y[h])>=(double)(y) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            iy = l;
+            
+            //
+            // Binary search in the [ z[0], ..., z[n-2] ] (z[n-1] is not included)
+            //
+            l = 0;
+            r = c.l-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.z[h])>=(double)(z) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            iz = l;
+            xd = (x-c.x[ix])/(c.x[ix+1]-c.x[ix]);
+            yd = (y-c.y[iy])/(c.y[iy+1]-c.y[iy]);
+            zd = (z-c.z[iz])/(c.z[iz+1]-c.z[iz]);
+            
+            //
+            // Trilinear interpolation
+            //
+            if( c.stype==-1 )
+            {
+                c0 = c.f[c.n*(c.m*iz+iy)+ix]*(1-xd)+c.f[c.n*(c.m*iz+iy)+(ix+1)]*xd;
+                c1 = c.f[c.n*(c.m*iz+(iy+1))+ix]*(1-xd)+c.f[c.n*(c.m*iz+(iy+1))+(ix+1)]*xd;
+                c2 = c.f[c.n*(c.m*(iz+1)+iy)+ix]*(1-xd)+c.f[c.n*(c.m*(iz+1)+iy)+(ix+1)]*xd;
+                c3 = c.f[c.n*(c.m*(iz+1)+(iy+1))+ix]*(1-xd)+c.f[c.n*(c.m*(iz+1)+(iy+1))+(ix+1)]*xd;
+                c0 = c0*(1-yd)+c1*yd;
+                c1 = c2*(1-yd)+c3*yd;
+                f = c0*(1-zd)+c1*zd;
+            }
+        }
+
+
+    }
+    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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            }
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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;
+            double c0 = 0;
+            double s0 = 0;
+            double va = 0;
+            double vb = 0;
+            double[] vai = new double[0];
+            double[] vbi = new double[0];
+            double minx = 0;
+            double maxx = 0;
+            int i_ = 0;
+
+            a = new double[0];
+
+            
+            //
+            // We have barycentric model built using set of points X[], and we
+            // want to convert it to power basis centered about point  C  with
+            // scale S: I-th basis function is ((X-C)/S)^i.
+            //
+            // We use following three-stage algorithm:
+            //
+            // 1. we build Chebyshev representation of polynomial using
+            //    intermediate center C0 and scale S0, which are derived from X[]:
+            //    C0 = 0.5*(min(X)+max(X)), S0 = 0.5*(max(X)-min(X)). Chebyshev
+            //    representation is built by sampling points around center C0,
+            //    with typical distance between them proportional to S0.
+            // 2. then we transform form Chebyshev basis to intermediate power
+            //    basis, using same center/scale C0/S0.
+            // 3. after that, we apply linear transformation to intermediate
+            //    power basis which moves it to final center/scale C/S.
+            //
+            // The idea of such multi-stage algorithm is that it is much easier to
+            // transform barycentric model to Chebyshev basis, and only later to
+            // power basis, than transforming it directly to power basis. It is
+            // also more numerically stable to sample points using intermediate C0/S0,
+            // which are derived from user-supplied model, than using "final" C/S,
+            // which may be unsuitable for sampling (say, if S=1, we may have stability
+            // problems when working with models built from dataset with non-unit
+            // scale of abscissas).
+            //
+            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!");
+            
+            //
+            // Select intermediate center/scale
+            //
+            minx = p.x[0];
+            maxx = p.x[0];
+            for(i=1; i<=p.n-1; i++)
+            {
+                minx = Math.Min(minx, p.x[i]);
+                maxx = Math.Max(maxx, p.x[i]);
+            }
+            if( (double)(minx)==(double)(maxx) )
+            {
+                c0 = minx;
+                s0 = 1.0;
+            }
+            else
+            {
+                c0 = 0.5*(maxx+minx);
+                s0 = 0.5*(maxx-minx);
+            }
+            
+            //
+            // Calculate function values on a Chebyshev grid using intermediate C0/S0
+            //
+            vp = new double[p.n+1];
+            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, s0*vx[i]+c0, _params);
+            }
+            
+            //
+            // 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;
+            }
+            
+            //
+            // Apply linear transformation which converts basis from intermediate
+            // one Fi=((x-C0)/S0)^i to final one Fi=((x-C)/S)^i.
+            //
+            // We have y=(x-C0)/S0, z=(x-C)/S, and coefficients A[] for basis Fi(y).
+            // Because we have y=A*z+B, for A=s/s0 and B=c/s0-c0/s0, we can perform
+            // substitution and get coefficients A_new[] in basis Fi(z).
+            //
+            alglib.ap.assert(alglib.ap.len(vp)>=p.n+1, "PolynomialBar2Pow: internal error");
+            alglib.ap.assert(alglib.ap.len(t)>=p.n, "PolynomialBar2Pow: internal error");
+            for(i=0; i<=p.n-1; i++)
+            {
+                t[i] = 0.0;
+            }
+            va = s/s0;
+            vb = c/s0-c0/s0;
+            vai = new double[p.n];
+            vbi = new double[p.n];
+            vai[0] = 1;
+            vbi[0] = 1;
+            for(k=1; k<=p.n-1; k++)
+            {
+                vai[k] = vai[k-1]*va;
+                vbi[k] = vbi[k-1]*vb;
+            }
+            for(k=0; k<=p.n-1; k++)
+            {
+                
+                //
+                // Generate set of binomial coefficients in VP[]
+                //
+                if( k>0 )
+                {
+                    vp[k] = 1;
+                    for(i=k-1; i>=1; i--)
+                    {
+                        vp[i] = vp[i]+vp[i-1];
+                    }
+                    vp[0] = 1;
+                }
+                else
+                {
+                    vp[0] = 1;
+                }
+                
+                //
+                // Update T[] with expansion of K-th basis function
+                //
+                for(i=0; i<=k; i++)
+                {
+                    t[i] = t[i]+a[k]*vai[i]*vbi[k-i]*vp[i];
+                }
+            }
+            for(k=0; k<=p.n-1; k++)
+            {
+                a[k] = t[k];
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "PolynomialBuild: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "PolynomialBuild: Y contains infinite or NaN values!");
+            tsort.tagsortfastr(ref x, ref y, ref sortrbuf, ref sortrbuf2, n, _params);
+            alglib.ap.assert(apserv.aredistinct(x, n, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params);
+                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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params);
+                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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params);
+                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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params), "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 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 : apobject
+        {
+            public double taskrcond;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public double maxerror;
+            public polynomialfitreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                polynomialfitreport _result = new polynomialfitreport();
+                _result.taskrcond = taskrcond;
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.avgrelerror = avgrelerror;
+                _result.maxerror = maxerror;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        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 : apobject
+        {
+            public double taskrcond;
+            public int dbest;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public double maxerror;
+            public barycentricfitreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                barycentricfitreport _result = new barycentricfitreport();
+                _result.taskrcond = taskrcond;
+                _result.dbest = dbest;
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.avgrelerror = avgrelerror;
+                _result.maxerror = maxerror;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Least squares fitting report. This structure contains informational fields
+        which are set by fitting functions provided by this unit.
+
+        Different functions initialize different sets of  fields,  so  you  should
+        read documentation on specific function you used in order  to  know  which
+        fields are initialized.
+
+            TaskRCond       reciprocal of task's condition number
+            IterationsCount number of internal iterations
+            
+            VarIdx          if user-supplied gradient contains errors  which  were
+                            detected by nonlinear fitter, this  field  is  set  to
+                            index  of  the  first  component  of gradient which is
+                            suspected to be spoiled by bugs.
+
+            RMSError        RMS error
+            AvgError        average error
+            AvgRelError     average relative error (for non-zero Y[I])
+            MaxError        maximum error
+
+            WRMSError       weighted RMS error
+
+            CovPar          covariance matrix for parameters, filled by some solvers
+            ErrPar          vector of errors in parameters, filled by some solvers
+            ErrCurve        vector of fit errors -  variability  of  the  best-fit
+                            curve, filled by some solvers.
+            Noise           vector of per-point noise estimates, filled by
+                            some solvers.
+            R2              coefficient of determination (non-weighted, non-adjusted),
+                            filled by some solvers.
+        *************************************************************************/
+        public class lsfitreport : apobject
+        {
+            public double taskrcond;
+            public int iterationscount;
+            public int varidx;
+            public double rmserror;
+            public double avgerror;
+            public double avgrelerror;
+            public double maxerror;
+            public double wrmserror;
+            public double[,] covpar;
+            public double[] errpar;
+            public double[] errcurve;
+            public double[] noise;
+            public double r2;
+            public lsfitreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+                covpar = new double[0,0];
+                errpar = new double[0];
+                errcurve = new double[0];
+                noise = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                lsfitreport _result = new lsfitreport();
+                _result.taskrcond = taskrcond;
+                _result.iterationscount = iterationscount;
+                _result.varidx = varidx;
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.avgrelerror = avgrelerror;
+                _result.maxerror = maxerror;
+                _result.wrmserror = wrmserror;
+                _result.covpar = (double[,])covpar.Clone();
+                _result.errpar = (double[])errpar.Clone();
+                _result.errcurve = (double[])errcurve.Clone();
+                _result.noise = (double[])noise.Clone();
+                _result.r2 = r2;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Nonlinear fitter.
+
+        You should use ALGLIB functions to work with fitter.
+        Never try to access its fields directly!
+        *************************************************************************/
+        public class lsfitstate : apobject
+        {
+            public int optalgo;
+            public int m;
+            public int k;
+            public double epsx;
+            public int maxits;
+            public double stpmax;
+            public bool xrep;
+            public double[] c0;
+            public double[] c1;
+            public double[] s;
+            public double[] bndl;
+            public double[] bndu;
+            public double[,] taskx;
+            public double[] tasky;
+            public int npoints;
+            public double[] taskw;
+            public int nweights;
+            public int wkind;
+            public int wits;
+            public double diffstep;
+            public double teststep;
+            public double[,] cleic;
+            public int nec;
+            public int nic;
+            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 double[] wcur;
+            public int[] tmpct;
+            public double[] tmp;
+            public double[] tmpf;
+            public double[,] tmpjac;
+            public double[,] tmpjacw;
+            public double tmpnoise;
+            public matinv.matinvreport invrep;
+            public int repiterationscount;
+            public int repterminationtype;
+            public int repvaridx;
+            public double reprmserror;
+            public double repavgerror;
+            public double repavgrelerror;
+            public double repmaxerror;
+            public double repwrmserror;
+            public lsfitreport rep;
+            public minlm.minlmstate optstate;
+            public minlm.minlmreport optrep;
+            public int prevnpt;
+            public int prevalgo;
+            public rcommstate rstate;
+            public lsfitstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                c0 = new double[0];
+                c1 = new double[0];
+                s = new double[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                taskx = new double[0,0];
+                tasky = new double[0];
+                taskw = new double[0];
+                cleic = new double[0,0];
+                x = new double[0];
+                c = new double[0];
+                g = new double[0];
+                h = new double[0,0];
+                wcur = new double[0];
+                tmpct = new int[0];
+                tmp = new double[0];
+                tmpf = new double[0];
+                tmpjac = new double[0,0];
+                tmpjacw = new double[0,0];
+                invrep = new matinv.matinvreport();
+                rep = new lsfitreport();
+                optstate = new minlm.minlmstate();
+                optrep = new minlm.minlmreport();
+                rstate = new rcommstate();
+            }
+            public override alglib.apobject make_copy()
+            {
+                lsfitstate _result = new lsfitstate();
+                _result.optalgo = optalgo;
+                _result.m = m;
+                _result.k = k;
+                _result.epsx = epsx;
+                _result.maxits = maxits;
+                _result.stpmax = stpmax;
+                _result.xrep = xrep;
+                _result.c0 = (double[])c0.Clone();
+                _result.c1 = (double[])c1.Clone();
+                _result.s = (double[])s.Clone();
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.taskx = (double[,])taskx.Clone();
+                _result.tasky = (double[])tasky.Clone();
+                _result.npoints = npoints;
+                _result.taskw = (double[])taskw.Clone();
+                _result.nweights = nweights;
+                _result.wkind = wkind;
+                _result.wits = wits;
+                _result.diffstep = diffstep;
+                _result.teststep = teststep;
+                _result.cleic = (double[,])cleic.Clone();
+                _result.nec = nec;
+                _result.nic = nic;
+                _result.xupdated = xupdated;
+                _result.needf = needf;
+                _result.needfg = needfg;
+                _result.needfgh = needfgh;
+                _result.pointindex = pointindex;
+                _result.x = (double[])x.Clone();
+                _result.c = (double[])c.Clone();
+                _result.f = f;
+                _result.g = (double[])g.Clone();
+                _result.h = (double[,])h.Clone();
+                _result.wcur = (double[])wcur.Clone();
+                _result.tmpct = (int[])tmpct.Clone();
+                _result.tmp = (double[])tmp.Clone();
+                _result.tmpf = (double[])tmpf.Clone();
+                _result.tmpjac = (double[,])tmpjac.Clone();
+                _result.tmpjacw = (double[,])tmpjacw.Clone();
+                _result.tmpnoise = tmpnoise;
+                _result.invrep = (matinv.matinvreport)invrep.make_copy();
+                _result.repiterationscount = repiterationscount;
+                _result.repterminationtype = repterminationtype;
+                _result.repvaridx = repvaridx;
+                _result.reprmserror = reprmserror;
+                _result.repavgerror = repavgerror;
+                _result.repavgrelerror = repavgrelerror;
+                _result.repmaxerror = repmaxerror;
+                _result.repwrmserror = repwrmserror;
+                _result.rep = (lsfitreport)rep.make_copy();
+                _result.optstate = (minlm.minlmstate)optstate.make_copy();
+                _result.optrep = (minlm.minlmreport)optrep.make_copy();
+                _result.prevnpt = prevnpt;
+                _result.prevalgo = prevalgo;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        This  subroutine fits piecewise linear curve to points with Ramer-Douglas-
+        Peucker algorithm, which stops after generating specified number of linear
+        sections.
+
+        IMPORTANT:
+        * it does NOT perform least-squares fitting; it  builds  curve,  but  this
+          curve does not minimize some least squares metric.  See  description  of
+          RDP algorithm (say, in Wikipedia) for more details on WHAT is performed.
+        * this function does NOT work with parametric curves  (i.e.  curves  which
+          can be represented as {X(t),Y(t)}. It works with curves   which  can  be
+          represented as Y(X). Thus,  it  is  impossible  to  model  figures  like
+          circles  with  this  functions.
+          If  you  want  to  work  with  parametric   curves,   you   should   use
+          ParametricRDPFixed() function provided  by  "Parametric"  subpackage  of
+          "Interpolation" package.
+
+        INPUT PARAMETERS:
+            X       -   array of X-coordinates:
+                        * at least N elements
+                        * can be unordered (points are automatically sorted)
+                        * this function may accept non-distinct X (see below for
+                          more information on handling of such inputs)
+            Y       -   array of Y-coordinates:
+                        * at least N elements
+            N       -   number of elements in X/Y
+            M       -   desired number of sections:
+                        * at most M sections are generated by this function
+                        * less than M sections can be generated if we have N<M
+                          (or some X are non-distinct).
+
+        OUTPUT PARAMETERS:
+            X2      -   X-values of corner points for piecewise approximation,
+                        has length NSections+1 or zero (for NSections=0).
+            Y2      -   Y-values of corner points,
+                        has length NSections+1 or zero (for NSections=0).
+            NSections-  number of sections found by algorithm, NSections<=M,
+                        NSections can be zero for degenerate datasets
+                        (N<=1 or all X[] are non-distinct).
+
+        NOTE: X2/Y2 are ordered arrays, i.e. (X2[0],Y2[0]) is  a  first  point  of
+              curve, (X2[NSection-1],Y2[NSection-1]) is the last point.
+
+          -- ALGLIB --
+             Copyright 02.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lstfitpiecewiselinearrdpfixed(double[] x,
+            double[] y,
+            int n,
+            int m,
+            ref double[] x2,
+            ref double[] y2,
+            ref int nsections,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int k0 = 0;
+            int k1 = 0;
+            int k2 = 0;
+            double[] buf0 = new double[0];
+            double[] buf1 = new double[0];
+            double[,] sections = new double[0,0];
+            double[] points = new double[0];
+            double v = 0;
+            int worstidx = 0;
+            double worsterror = 0;
+            int idx0 = 0;
+            int idx1 = 0;
+            double e0 = 0;
+            double e1 = 0;
+            double[] heaperrors = new double[0];
+            int[] heaptags = new int[0];
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            x2 = new double[0];
+            y2 = new double[0];
+            nsections = 0;
+
+            alglib.ap.assert(n>=0, "LSTFitPiecewiseLinearRDPFixed: N<0");
+            alglib.ap.assert(m>=1, "LSTFitPiecewiseLinearRDPFixed: M<1");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "LSTFitPiecewiseLinearRDPFixed: Length(X)<N");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LSTFitPiecewiseLinearRDPFixed: Length(Y)<N");
+            if( n<=1 )
+            {
+                nsections = 0;
+                return;
+            }
+            
+            //
+            // Sort points.
+            // Handle possible ties (tied values are replaced by their mean)
+            //
+            tsort.tagsortfastr(ref x, ref y, ref buf0, ref buf1, n, _params);
+            i = 0;
+            while( i<=n-1 )
+            {
+                j = i+1;
+                v = y[i];
+                while( j<=n-1 && (double)(x[j])==(double)(x[i]) )
+                {
+                    v = v+y[j];
+                    j = j+1;
+                }
+                v = v/(j-i);
+                for(k=i; k<=j-1; k++)
+                {
+                    y[k] = v;
+                }
+                i = j;
+            }
+            
+            //
+            // Handle degenerate case x[0]=x[N-1]
+            //
+            if( (double)(x[n-1])==(double)(x[0]) )
+            {
+                nsections = 0;
+                return;
+            }
+            
+            //
+            // Prepare first section
+            //
+            rdpanalyzesection(x, y, 0, n-1, ref worstidx, ref worsterror, _params);
+            sections = new double[m, 4];
+            heaperrors = new double[m];
+            heaptags = new int[m];
+            nsections = 1;
+            sections[0,0] = 0;
+            sections[0,1] = n-1;
+            sections[0,2] = worstidx;
+            sections[0,3] = worsterror;
+            heaperrors[0] = worsterror;
+            heaptags[0] = 0;
+            alglib.ap.assert((double)(sections[0,1])==(double)(n-1), "RDP algorithm: integrity check failed");
+            
+            //
+            // Main loop.
+            // Repeatedly find section with worst error and divide it.
+            // Terminate after M-th section, or because of other reasons (see loop internals).
+            //
+            while( nsections<m )
+            {
+                
+                //
+                // Break if worst section has zero error.
+                // Store index of worst section to K.
+                //
+                if( (double)(heaperrors[0])==(double)(0) )
+                {
+                    break;
+                }
+                k = heaptags[0];
+                
+                //
+                // K-th section is divided in two:
+                // * first  one spans interval from X[Sections[K,0]] to X[Sections[K,2]]
+                // * second one spans interval from X[Sections[K,2]] to X[Sections[K,1]]
+                //
+                // First section is stored at K-th position, second one is appended to the table.
+                // Then we update heap which stores pairs of (error,section_index)
+                //
+                k0 = (int)Math.Round(sections[k,0]);
+                k1 = (int)Math.Round(sections[k,1]);
+                k2 = (int)Math.Round(sections[k,2]);
+                rdpanalyzesection(x, y, k0, k2, ref idx0, ref e0, _params);
+                rdpanalyzesection(x, y, k2, k1, ref idx1, ref e1, _params);
+                sections[k,0] = k0;
+                sections[k,1] = k2;
+                sections[k,2] = idx0;
+                sections[k,3] = e0;
+                tsort.tagheapreplacetopi(ref heaperrors, ref heaptags, nsections, e0, k, _params);
+                sections[nsections,0] = k2;
+                sections[nsections,1] = k1;
+                sections[nsections,2] = idx1;
+                sections[nsections,3] = e1;
+                tsort.tagheappushi(ref heaperrors, ref heaptags, ref nsections, e1, nsections, _params);
+            }
+            
+            //
+            // Convert from sections to points
+            //
+            points = new double[nsections+1];
+            k = (int)Math.Round(sections[0,1]);
+            for(i=0; i<=nsections-1; i++)
+            {
+                points[i] = (int)Math.Round(sections[i,0]);
+                if( (double)(x[(int)Math.Round(sections[i,1])])>(double)(x[k]) )
+                {
+                    k = (int)Math.Round(sections[i,1]);
+                }
+            }
+            points[nsections] = k;
+            tsort.tagsortfast(ref points, ref buf0, nsections+1, _params);
+            
+            //
+            // Output sections:
+            // * first NSection elements of X2/Y2 are filled by x/y at left boundaries of sections
+            // * last element of X2/Y2 is filled by right boundary of rightmost section
+            // * X2/Y2 is sorted by ascending of X2
+            //
+            x2 = new double[nsections+1];
+            y2 = new double[nsections+1];
+            for(i=0; i<=nsections; i++)
+            {
+                x2[i] = x[(int)Math.Round(points[i])];
+                y2[i] = y[(int)Math.Round(points[i])];
+            }
+        }
+
+
+        /*************************************************************************
+        This  subroutine fits piecewise linear curve to points with Ramer-Douglas-
+        Peucker algorithm, which stops after achieving desired precision.
+
+        IMPORTANT:
+        * it performs non-least-squares fitting; it builds curve, but  this  curve
+          does not minimize some least squares  metric.  See  description  of  RDP
+          algorithm (say, in Wikipedia) for more details on WHAT is performed.
+        * this function does NOT work with parametric curves  (i.e.  curves  which
+          can be represented as {X(t),Y(t)}. It works with curves   which  can  be
+          represented as Y(X). Thus, it is impossible to model figures like circles
+          with this functions.
+          If  you  want  to  work  with  parametric   curves,   you   should   use
+          ParametricRDPFixed() function provided  by  "Parametric"  subpackage  of
+          "Interpolation" package.
+
+        INPUT PARAMETERS:
+            X       -   array of X-coordinates:
+                        * at least N elements
+                        * can be unordered (points are automatically sorted)
+                        * this function may accept non-distinct X (see below for
+                          more information on handling of such inputs)
+            Y       -   array of Y-coordinates:
+                        * at least N elements
+            N       -   number of elements in X/Y
+            Eps     -   positive number, desired precision.
+            
+
+        OUTPUT PARAMETERS:
+            X2      -   X-values of corner points for piecewise approximation,
+                        has length NSections+1 or zero (for NSections=0).
+            Y2      -   Y-values of corner points,
+                        has length NSections+1 or zero (for NSections=0).
+            NSections-  number of sections found by algorithm,
+                        NSections can be zero for degenerate datasets
+                        (N<=1 or all X[] are non-distinct).
+
+        NOTE: X2/Y2 are ordered arrays, i.e. (X2[0],Y2[0]) is  a  first  point  of
+              curve, (X2[NSection-1],Y2[NSection-1]) is the last point.
+
+          -- ALGLIB --
+             Copyright 02.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lstfitpiecewiselinearrdp(double[] x,
+            double[] y,
+            int n,
+            double eps,
+            ref double[] x2,
+            ref double[] y2,
+            ref int nsections,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double[] buf0 = new double[0];
+            double[] buf1 = new double[0];
+            double[] xtmp = new double[0];
+            double[] ytmp = new double[0];
+            double v = 0;
+            int npts = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            x2 = new double[0];
+            y2 = new double[0];
+            nsections = 0;
+
+            alglib.ap.assert(n>=0, "LSTFitPiecewiseLinearRDP: N<0");
+            alglib.ap.assert((double)(eps)>(double)(0), "LSTFitPiecewiseLinearRDP: Eps<=0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "LSTFitPiecewiseLinearRDP: Length(X)<N");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LSTFitPiecewiseLinearRDP: Length(Y)<N");
+            if( n<=1 )
+            {
+                nsections = 0;
+                return;
+            }
+            
+            //
+            // Sort points.
+            // Handle possible ties (tied values are replaced by their mean)
+            //
+            tsort.tagsortfastr(ref x, ref y, ref buf0, ref buf1, n, _params);
+            i = 0;
+            while( i<=n-1 )
+            {
+                j = i+1;
+                v = y[i];
+                while( j<=n-1 && (double)(x[j])==(double)(x[i]) )
+                {
+                    v = v+y[j];
+                    j = j+1;
+                }
+                v = v/(j-i);
+                for(k=i; k<=j-1; k++)
+                {
+                    y[k] = v;
+                }
+                i = j;
+            }
+            
+            //
+            // Handle degenerate case x[0]=x[N-1]
+            //
+            if( (double)(x[n-1])==(double)(x[0]) )
+            {
+                nsections = 0;
+                return;
+            }
+            
+            //
+            // Prepare data for recursive algorithm
+            //
+            xtmp = new double[n];
+            ytmp = new double[n];
+            npts = 2;
+            xtmp[0] = x[0];
+            ytmp[0] = y[0];
+            xtmp[1] = x[n-1];
+            ytmp[1] = y[n-1];
+            rdprecursive(x, y, 0, n-1, eps, xtmp, ytmp, ref npts, _params);
+            
+            //
+            // Output sections:
+            // * first NSection elements of X2/Y2 are filled by x/y at left boundaries of sections
+            // * last element of X2/Y2 is filled by right boundary of rightmost section
+            // * X2/Y2 is sorted by ascending of X2
+            //
+            nsections = npts-1;
+            x2 = new double[npts];
+            y2 = new double[npts];
+            for(i=0; i<=nsections; i++)
+            {
+                x2[i] = xtmp[i];
+                y2[i] = ytmp[i];
+            }
+            tsort.tagsortfastr(ref x2, ref y2, ref buf0, ref buf1, npts, _params);
+        }
+
+
+        /*************************************************************************
+        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()
+
+        NOTES:
+            you can convert P from barycentric form  to  the  power  or  Chebyshev
+            basis with PolynomialBar2Pow() or PolynomialBar2Cheb() functions  from
+            POLINT subpackage.
+            
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params), "PolynomialFit: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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()
+
+        NOTES:
+            you can convert P from barycentric form  to  the  power  or  Chebyshev
+            basis with PolynomialBar2Pow() or PolynomialBar2Cheb() functions  from
+            POLINT subpackage.
+            
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "PolynomialFitWC: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "PolynomialFitWC: Y contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(w, n, _params), "PolynomialFitWC: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(xc, k, _params), "PolynomialFitWC: XC contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(yc, k, _params), "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.
+            //
+            intfitserv.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, _params);
+            internalchebyshevfit(x, y, w, n, xc, yc, dc, k, m, ref info, ref tmp, lrep, _params);
+            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, _params);
+            ratint.barycentriclintransx(p, 2/(xb-xa), -((xa+xb)/(xb-xa)), _params);
+            ratint.barycentriclintransy(p, sb-sa, sa, _params);
+            
+            //
+            // 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], _params)-yoriginal[i])/Math.Abs(yoriginal[i]);
+                    relcnt = relcnt+1;
+                }
+            }
+            if( relcnt!=0 )
+            {
+                rep.avgrelerror = rep.avgrelerror/relcnt;
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates value of four-parameter logistic (4PL)  model  at
+        specified point X. 4PL model has following form:
+
+            F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B))
+
+        INPUT PARAMETERS:
+            X       -   current point, X>=0:
+                        * zero X is correctly handled even for B<=0
+                        * negative X results in exception.
+            A, B, C, D- parameters of 4PL model:
+                        * A is unconstrained
+                        * B is unconstrained; zero or negative values are handled
+                          correctly.
+                        * C>0, non-positive value results in exception
+                        * D is unconstrained
+                        
+        RESULT:
+            model value at X
+
+        NOTE: if B=0, denominator is assumed to be equal to 2.0 even  for  zero  X
+              (strictly speaking, 0^0 is undefined).
+
+        NOTE: this function also throws exception  if  all  input  parameters  are
+              correct, but overflow was detected during calculations.
+              
+        NOTE: this function performs a lot of checks;  if  you  need  really  high
+              performance, consider evaluating model  yourself,  without  checking
+              for degenerate cases.
+              
+            
+          -- ALGLIB PROJECT --
+             Copyright 14.05.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static double logisticcalc4(double x,
+            double a,
+            double b,
+            double c,
+            double d,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(math.isfinite(x), "LogisticCalc4: X is not finite");
+            alglib.ap.assert(math.isfinite(a), "LogisticCalc4: A is not finite");
+            alglib.ap.assert(math.isfinite(b), "LogisticCalc4: B is not finite");
+            alglib.ap.assert(math.isfinite(c), "LogisticCalc4: C is not finite");
+            alglib.ap.assert(math.isfinite(d), "LogisticCalc4: D is not finite");
+            alglib.ap.assert((double)(x)>=(double)(0), "LogisticCalc4: X is negative");
+            alglib.ap.assert((double)(c)>(double)(0), "LogisticCalc4: C is non-positive");
+            
+            //
+            // Check for degenerate cases
+            //
+            if( (double)(b)==(double)(0) )
+            {
+                result = 0.5*(a+d);
+                return result;
+            }
+            if( (double)(x)==(double)(0) )
+            {
+                if( (double)(b)>(double)(0) )
+                {
+                    result = a;
+                }
+                else
+                {
+                    result = d;
+                }
+                return result;
+            }
+            
+            //
+            // General case
+            //
+            result = d+(a-d)/(1.0+Math.Pow(x/c, b));
+            alglib.ap.assert(math.isfinite(result), "LogisticCalc4: overflow during calculations");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function calculates value of five-parameter logistic (5PL)  model  at
+        specified point X. 5PL model has following form:
+
+            F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G)
+
+        INPUT PARAMETERS:
+            X       -   current point, X>=0:
+                        * zero X is correctly handled even for B<=0
+                        * negative X results in exception.
+            A, B, C, D, G- parameters of 5PL model:
+                        * A is unconstrained
+                        * B is unconstrained; zero or negative values are handled
+                          correctly.
+                        * C>0, non-positive value results in exception
+                        * D is unconstrained
+                        * G>0, non-positive value results in exception
+                        
+        RESULT:
+            model value at X
+
+        NOTE: if B=0, denominator is assumed to be equal to Power(2.0,G) even  for
+              zero X (strictly speaking, 0^0 is undefined).
+
+        NOTE: this function also throws exception  if  all  input  parameters  are
+              correct, but overflow was detected during calculations.
+              
+        NOTE: this function performs a lot of checks;  if  you  need  really  high
+              performance, consider evaluating model  yourself,  without  checking
+              for degenerate cases.
+              
+            
+          -- ALGLIB PROJECT --
+             Copyright 14.05.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static double logisticcalc5(double x,
+            double a,
+            double b,
+            double c,
+            double d,
+            double g,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(math.isfinite(x), "LogisticCalc5: X is not finite");
+            alglib.ap.assert(math.isfinite(a), "LogisticCalc5: A is not finite");
+            alglib.ap.assert(math.isfinite(b), "LogisticCalc5: B is not finite");
+            alglib.ap.assert(math.isfinite(c), "LogisticCalc5: C is not finite");
+            alglib.ap.assert(math.isfinite(d), "LogisticCalc5: D is not finite");
+            alglib.ap.assert(math.isfinite(g), "LogisticCalc5: G is not finite");
+            alglib.ap.assert((double)(x)>=(double)(0), "LogisticCalc5: X is negative");
+            alglib.ap.assert((double)(c)>(double)(0), "LogisticCalc5: C is non-positive");
+            alglib.ap.assert((double)(g)>(double)(0), "LogisticCalc5: G is non-positive");
+            
+            //
+            // Check for degenerate cases
+            //
+            if( (double)(b)==(double)(0) )
+            {
+                result = d+(a-d)/Math.Pow(2.0, g);
+                return result;
+            }
+            if( (double)(x)==(double)(0) )
+            {
+                if( (double)(b)>(double)(0) )
+                {
+                    result = a;
+                }
+                else
+                {
+                    result = d;
+                }
+                return result;
+            }
+            
+            //
+            // General case
+            //
+            result = d+(a-d)/Math.Pow(1.0+Math.Pow(x/c, b), g);
+            alglib.ap.assert(math.isfinite(result), "LogisticCalc5: overflow during calculations");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function fits four-parameter logistic (4PL) model  to  data  provided
+        by user. 4PL model has following form:
+
+            F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B))
+
+        Here:
+            * A, D - unconstrained (see LogisticFit4EC() for constrained 4PL)
+            * B>=0
+            * C>0
+            
+        IMPORTANT: output of this function is constrained in  such  way that  B>0.
+                   Because 4PL model is symmetric with respect to B, there  is  no
+                   need to explore  B<0.  Constraining  B  makes  algorithm easier
+                   to stabilize and debug.
+                   Users  who  for  some  reason  prefer to work with negative B's
+                   should transform output themselves (swap A and D, replace B  by
+                   -B).
+                   
+        4PL fitting is implemented as follows:
+        * we perform small number of restarts from random locations which helps to
+          solve problem of bad local extrema. Locations are only partially  random
+          - we use input data to determine good  initial  guess,  but  we  include
+          controlled amount of randomness.
+        * we perform Levenberg-Marquardt fitting with very  tight  constraints  on
+          parameters B and C - it allows us to find good  initial  guess  for  the
+          second stage without risk of running into "flat spot".
+        * second  Levenberg-Marquardt  round  is   performed   without   excessive
+          constraints. Results from the previous round are used as initial guess.
+        * after fitting is done, we compare results with best values found so far,
+          rewrite "best solution" if needed, and move to next random location.
+          
+        Overall algorithm is very stable and is not prone to  bad  local  extrema.
+        Furthermore, it automatically scales when input data have  very  large  or
+        very small range.
+
+        INPUT PARAMETERS:
+            X       -   array[N], stores X-values.
+                        MUST include only non-negative numbers  (but  may  include
+                        zero values). Can be unsorted.
+            Y       -   array[N], values to fit.
+            N       -   number of points. If N is less than  length  of  X/Y, only
+                        leading N elements are used.
+                        
+        OUTPUT PARAMETERS:
+            A, B, C, D- parameters of 4PL model
+            Rep     -   fitting report. This structure has many fields,  but  ONLY
+                        ONES LISTED BELOW ARE SET:
+                        * Rep.IterationsCount - number of iterations performed
+                        * Rep.RMSError - root-mean-square error
+                        * Rep.AvgError - average absolute error
+                        * Rep.AvgRelError - average relative error (calculated for
+                          non-zero Y-values)
+                        * Rep.MaxError - maximum absolute error
+                        * Rep.R2 - coefficient of determination,  R-squared.  This
+                          coefficient   is  calculated  as  R2=1-RSS/TSS  (in case
+                          of nonlinear  regression  there  are  multiple  ways  to
+                          define R2, each of them giving different results).
+
+        NOTE: for stability reasons the B parameter is restricted by [1/1000,1000]
+              range. It prevents  algorithm from making trial steps  deep into the
+              area of bad parameters.
+
+        NOTE: after  you  obtained  coefficients,  you  can  evaluate  model  with
+              LogisticCalc4() function.
+
+        NOTE: if you need better control over fitting process than provided by this
+              function, you may use LogisticFit45X().
+                        
+        NOTE: step is automatically scaled according to scale of parameters  being
+              fitted before we compare its length with EpsX. Thus,  this  function
+              can be used to fit data with very small or very large values without
+              changing EpsX.
+            
+
+          -- ALGLIB PROJECT --
+             Copyright 14.02.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void logisticfit4(double[] x,
+            double[] y,
+            int n,
+            ref double a,
+            ref double b,
+            ref double c,
+            ref double d,
+            lsfitreport rep,
+            alglib.xparams _params)
+        {
+            double g = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            a = 0;
+            b = 0;
+            c = 0;
+            d = 0;
+
+            logisticfit45x(x, y, n, Double.NaN, Double.NaN, true, 0.0, 0.0, 0, ref a, ref b, ref c, ref d, ref g, rep, _params);
+        }
+
+
+        /*************************************************************************
+        This function fits four-parameter logistic (4PL) model  to  data  provided
+        by user, with optional constraints on parameters A and D.  4PL  model  has
+        following form:
+
+            F(x|A,B,C,D) = D+(A-D)/(1+Power(x/C,B))
+
+        Here:
+            * A, D - with optional equality constraints
+            * B>=0
+            * C>0
+            
+        IMPORTANT: output of this function is constrained in  such  way that  B>0.
+                   Because 4PL model is symmetric with respect to B, there  is  no
+                   need to explore  B<0.  Constraining  B  makes  algorithm easier
+                   to stabilize and debug.
+                   Users  who  for  some  reason  prefer to work with negative B's
+                   should transform output themselves (swap A and D, replace B  by
+                   -B).
+                   
+        4PL fitting is implemented as follows:
+        * we perform small number of restarts from random locations which helps to
+          solve problem of bad local extrema. Locations are only partially  random
+          - we use input data to determine good  initial  guess,  but  we  include
+          controlled amount of randomness.
+        * we perform Levenberg-Marquardt fitting with very  tight  constraints  on
+          parameters B and C - it allows us to find good  initial  guess  for  the
+          second stage without risk of running into "flat spot".
+        * second  Levenberg-Marquardt  round  is   performed   without   excessive
+          constraints. Results from the previous round are used as initial guess.
+        * after fitting is done, we compare results with best values found so far,
+          rewrite "best solution" if needed, and move to next random location.
+          
+        Overall algorithm is very stable and is not prone to  bad  local  extrema.
+        Furthermore, it automatically scales when input data have  very  large  or
+        very small range.
+
+        INPUT PARAMETERS:
+            X       -   array[N], stores X-values.
+                        MUST include only non-negative numbers  (but  may  include
+                        zero values). Can be unsorted.
+            Y       -   array[N], values to fit.
+            N       -   number of points. If N is less than  length  of  X/Y, only
+                        leading N elements are used.
+            CnstrLeft-  optional equality constraint for model value at the   left
+                        boundary (at X=0). Specify NAN (Not-a-Number)  if  you  do
+                        not need constraint on the model value at X=0 (in C++  you
+                        can pass alglib::fp_nan as parameter, in  C#  it  will  be
+                        Double.NaN).
+                        See  below,  section  "EQUALITY  CONSTRAINTS"   for   more
+                        information about constraints.
+            CnstrRight- optional equality constraint for model value at X=infinity.
+                        Specify NAN (Not-a-Number) if you do not  need  constraint
+                        on the model value (in C++  you can pass alglib::fp_nan as
+                        parameter, in  C# it will  be Double.NaN).
+                        See  below,  section  "EQUALITY  CONSTRAINTS"   for   more
+                        information about constraints.
+                        
+        OUTPUT PARAMETERS:
+            A, B, C, D- parameters of 4PL model
+            Rep     -   fitting report. This structure has many fields,  but  ONLY
+                        ONES LISTED BELOW ARE SET:
+                        * Rep.IterationsCount - number of iterations performed
+                        * Rep.RMSError - root-mean-square error
+                        * Rep.AvgError - average absolute error
+                        * Rep.AvgRelError - average relative error (calculated for
+                          non-zero Y-values)
+                        * Rep.MaxError - maximum absolute error
+                        * Rep.R2 - coefficient of determination,  R-squared.  This
+                          coefficient   is  calculated  as  R2=1-RSS/TSS  (in case
+                          of nonlinear  regression  there  are  multiple  ways  to
+                          define R2, each of them giving different results).
+
+        NOTE: for stability reasons the B parameter is restricted by [1/1000,1000]
+              range. It prevents  algorithm from making trial steps  deep into the
+              area of bad parameters.
+
+        NOTE: after  you  obtained  coefficients,  you  can  evaluate  model  with
+              LogisticCalc4() function.
+
+        NOTE: if you need better control over fitting process than provided by this
+              function, you may use LogisticFit45X().
+                        
+        NOTE: step is automatically scaled according to scale of parameters  being
+              fitted before we compare its length with EpsX. Thus,  this  function
+              can be used to fit data with very small or very large values without
+              changing EpsX.
+
+        EQUALITY CONSTRAINTS ON PARAMETERS
+
+        4PL/5PL solver supports equality constraints on model values at  the  left
+        boundary (X=0) and right  boundary  (X=infinity).  These  constraints  are
+        completely optional and you can specify both of them, only  one  -  or  no
+        constraints at all.
+
+        Parameter  CnstrLeft  contains  left  constraint (or NAN for unconstrained
+        fitting), and CnstrRight contains right  one.  For  4PL,  left  constraint
+        ALWAYS corresponds to parameter A, and right one is ALWAYS  constraint  on
+        D. That's because 4PL model is normalized in such way that B>=0.
+            
+
+          -- ALGLIB PROJECT --
+             Copyright 14.02.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void logisticfit4ec(double[] x,
+            double[] y,
+            int n,
+            double cnstrleft,
+            double cnstrright,
+            ref double a,
+            ref double b,
+            ref double c,
+            ref double d,
+            lsfitreport rep,
+            alglib.xparams _params)
+        {
+            double g = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            a = 0;
+            b = 0;
+            c = 0;
+            d = 0;
+
+            logisticfit45x(x, y, n, cnstrleft, cnstrright, true, 0.0, 0.0, 0, ref a, ref b, ref c, ref d, ref g, rep, _params);
+        }
+
+
+        /*************************************************************************
+        This function fits five-parameter logistic (5PL) model  to  data  provided
+        by user. 5PL model has following form:
+
+            F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G)
+
+        Here:
+            * A, D - unconstrained
+            * B - unconstrained
+            * C>0
+            * G>0
+            
+        IMPORTANT: unlike in  4PL  fitting,  output  of  this  function   is   NOT
+                   constrained in  such  way that B is guaranteed to be  positive.
+                   Furthermore,  unlike  4PL,  5PL  model  is  NOT  symmetric with
+                   respect to B, so you can NOT transform model to equivalent one,
+                   with B having desired sign (>0 or <0).
+            
+        5PL fitting is implemented as follows:
+        * we perform small number of restarts from random locations which helps to
+          solve problem of bad local extrema. Locations are only partially  random
+          - we use input data to determine good  initial  guess,  but  we  include
+          controlled amount of randomness.
+        * we perform Levenberg-Marquardt fitting with very  tight  constraints  on
+          parameters B and C - it allows us to find good  initial  guess  for  the
+          second stage without risk of running into "flat spot".  Parameter  G  is
+          fixed at G=1.
+        * second  Levenberg-Marquardt  round  is   performed   without   excessive
+          constraints on B and C, but with G still equal to 1.  Results  from  the
+          previous round are used as initial guess.
+        * third Levenberg-Marquardt round relaxes constraints on G  and  tries  two
+          different models - one with B>0 and one with B<0.
+        * after fitting is done, we compare results with best values found so far,
+          rewrite "best solution" if needed, and move to next random location.
+          
+        Overall algorithm is very stable and is not prone to  bad  local  extrema.
+        Furthermore, it automatically scales when input data have  very  large  or
+        very small range.
+
+        INPUT PARAMETERS:
+            X       -   array[N], stores X-values.
+                        MUST include only non-negative numbers  (but  may  include
+                        zero values). Can be unsorted.
+            Y       -   array[N], values to fit.
+            N       -   number of points. If N is less than  length  of  X/Y, only
+                        leading N elements are used.
+                        
+        OUTPUT PARAMETERS:
+            A,B,C,D,G-  parameters of 5PL model
+            Rep     -   fitting report. This structure has many fields,  but  ONLY
+                        ONES LISTED BELOW ARE SET:
+                        * Rep.IterationsCount - number of iterations performed
+                        * Rep.RMSError - root-mean-square error
+                        * Rep.AvgError - average absolute error
+                        * Rep.AvgRelError - average relative error (calculated for
+                          non-zero Y-values)
+                        * Rep.MaxError - maximum absolute error
+                        * Rep.R2 - coefficient of determination,  R-squared.  This
+                          coefficient   is  calculated  as  R2=1-RSS/TSS  (in case
+                          of nonlinear  regression  there  are  multiple  ways  to
+                          define R2, each of them giving different results).
+
+        NOTE: for better stability B  parameter is restricted by [+-1/1000,+-1000]
+              range, and G is restricted by [1/10,10] range. It prevents algorithm
+              from making trial steps deep into the area of bad parameters.
+
+        NOTE: after  you  obtained  coefficients,  you  can  evaluate  model  with
+              LogisticCalc5() function.
+
+        NOTE: if you need better control over fitting process than provided by this
+              function, you may use LogisticFit45X().
+                        
+        NOTE: step is automatically scaled according to scale of parameters  being
+              fitted before we compare its length with EpsX. Thus,  this  function
+              can be used to fit data with very small or very large values without
+              changing EpsX.
+            
+
+          -- ALGLIB PROJECT --
+             Copyright 14.02.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void logisticfit5(double[] x,
+            double[] y,
+            int n,
+            ref double a,
+            ref double b,
+            ref double c,
+            ref double d,
+            ref double g,
+            lsfitreport rep,
+            alglib.xparams _params)
+        {
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            a = 0;
+            b = 0;
+            c = 0;
+            d = 0;
+            g = 0;
+
+            logisticfit45x(x, y, n, Double.NaN, Double.NaN, false, 0.0, 0.0, 0, ref a, ref b, ref c, ref d, ref g, rep, _params);
+        }
+
+
+        /*************************************************************************
+        This function fits five-parameter logistic (5PL) model  to  data  provided
+        by user, subject to optional equality constraints on parameters A  and  D.
+        5PL model has following form:
+
+            F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G)
+
+        Here:
+            * A, D - with optional equality constraints
+            * B - unconstrained
+            * C>0
+            * G>0
+            
+        IMPORTANT: unlike in  4PL  fitting,  output  of  this  function   is   NOT
+                   constrained in  such  way that B is guaranteed to be  positive.
+                   Furthermore,  unlike  4PL,  5PL  model  is  NOT  symmetric with
+                   respect to B, so you can NOT transform model to equivalent one,
+                   with B having desired sign (>0 or <0).
+            
+        5PL fitting is implemented as follows:
+        * we perform small number of restarts from random locations which helps to
+          solve problem of bad local extrema. Locations are only partially  random
+          - we use input data to determine good  initial  guess,  but  we  include
+          controlled amount of randomness.
+        * we perform Levenberg-Marquardt fitting with very  tight  constraints  on
+          parameters B and C - it allows us to find good  initial  guess  for  the
+          second stage without risk of running into "flat spot".  Parameter  G  is
+          fixed at G=1.
+        * second  Levenberg-Marquardt  round  is   performed   without   excessive
+          constraints on B and C, but with G still equal to 1.  Results  from  the
+          previous round are used as initial guess.
+        * third Levenberg-Marquardt round relaxes constraints on G  and  tries  two
+          different models - one with B>0 and one with B<0.
+        * after fitting is done, we compare results with best values found so far,
+          rewrite "best solution" if needed, and move to next random location.
+          
+        Overall algorithm is very stable and is not prone to  bad  local  extrema.
+        Furthermore, it automatically scales when input data have  very  large  or
+        very small range.
+
+        INPUT PARAMETERS:
+            X       -   array[N], stores X-values.
+                        MUST include only non-negative numbers  (but  may  include
+                        zero values). Can be unsorted.
+            Y       -   array[N], values to fit.
+            N       -   number of points. If N is less than  length  of  X/Y, only
+                        leading N elements are used.
+            CnstrLeft-  optional equality constraint for model value at the   left
+                        boundary (at X=0). Specify NAN (Not-a-Number)  if  you  do
+                        not need constraint on the model value at X=0 (in C++  you
+                        can pass alglib::fp_nan as parameter, in  C#  it  will  be
+                        Double.NaN).
+                        See  below,  section  "EQUALITY  CONSTRAINTS"   for   more
+                        information about constraints.
+            CnstrRight- optional equality constraint for model value at X=infinity.
+                        Specify NAN (Not-a-Number) if you do not  need  constraint
+                        on the model value (in C++  you can pass alglib::fp_nan as
+                        parameter, in  C# it will  be Double.NaN).
+                        See  below,  section  "EQUALITY  CONSTRAINTS"   for   more
+                        information about constraints.
+                        
+        OUTPUT PARAMETERS:
+            A,B,C,D,G-  parameters of 5PL model
+            Rep     -   fitting report. This structure has many fields,  but  ONLY
+                        ONES LISTED BELOW ARE SET:
+                        * Rep.IterationsCount - number of iterations performed
+                        * Rep.RMSError - root-mean-square error
+                        * Rep.AvgError - average absolute error
+                        * Rep.AvgRelError - average relative error (calculated for
+                          non-zero Y-values)
+                        * Rep.MaxError - maximum absolute error
+                        * Rep.R2 - coefficient of determination,  R-squared.  This
+                          coefficient   is  calculated  as  R2=1-RSS/TSS  (in case
+                          of nonlinear  regression  there  are  multiple  ways  to
+                          define R2, each of them giving different results).
+
+        NOTE: for better stability B  parameter is restricted by [+-1/1000,+-1000]
+              range, and G is restricted by [1/10,10] range. It prevents algorithm
+              from making trial steps deep into the area of bad parameters.
+
+        NOTE: after  you  obtained  coefficients,  you  can  evaluate  model  with
+              LogisticCalc5() function.
+
+        NOTE: if you need better control over fitting process than provided by this
+              function, you may use LogisticFit45X().
+                        
+        NOTE: step is automatically scaled according to scale of parameters  being
+              fitted before we compare its length with EpsX. Thus,  this  function
+              can be used to fit data with very small or very large values without
+              changing EpsX.
+
+        EQUALITY CONSTRAINTS ON PARAMETERS
+
+        5PL solver supports equality constraints on model  values  at   the   left
+        boundary (X=0) and right  boundary  (X=infinity).  These  constraints  are
+        completely optional and you can specify both of them, only  one  -  or  no
+        constraints at all.
+
+        Parameter  CnstrLeft  contains  left  constraint (or NAN for unconstrained
+        fitting), and CnstrRight contains right  one.
+
+        Unlike 4PL one, 5PL model is NOT symmetric with respect to  change in sign
+        of B. Thus, negative B's are possible, and left constraint  may  constrain
+        parameter A (for positive B's)  -  or  parameter  D  (for  negative  B's).
+        Similarly changes meaning of right constraint.
+
+        You do not have to decide what parameter to  constrain  -  algorithm  will
+        automatically determine correct parameters as fitting progresses. However,
+        question highlighted above is important when you interpret fitting results.
+            
+
+          -- ALGLIB PROJECT --
+             Copyright 14.02.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void logisticfit5ec(double[] x,
+            double[] y,
+            int n,
+            double cnstrleft,
+            double cnstrright,
+            ref double a,
+            ref double b,
+            ref double c,
+            ref double d,
+            ref double g,
+            lsfitreport rep,
+            alglib.xparams _params)
+        {
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            a = 0;
+            b = 0;
+            c = 0;
+            d = 0;
+            g = 0;
+
+            logisticfit45x(x, y, n, cnstrleft, cnstrright, false, 0.0, 0.0, 0, ref a, ref b, ref c, ref d, ref g, rep, _params);
+        }
+
+
+        /*************************************************************************
+        This is "expert" 4PL/5PL fitting function, which can be used if  you  need
+        better control over fitting process than provided  by  LogisticFit4()  or
+        LogisticFit5().
+
+        This function fits model of the form
+
+            F(x|A,B,C,D)   = D+(A-D)/(1+Power(x/C,B))           (4PL model)
+
+        or
+
+            F(x|A,B,C,D,G) = D+(A-D)/Power(1+Power(x/C,B),G)    (5PL model)
+            
+        Here:
+            * A, D - unconstrained
+            * B>=0 for 4PL, unconstrained for 5PL
+            * C>0
+            * G>0 (if present)
+
+        INPUT PARAMETERS:
+            X       -   array[N], stores X-values.
+                        MUST include only non-negative numbers  (but  may  include
+                        zero values). Can be unsorted.
+            Y       -   array[N], values to fit.
+            N       -   number of points. If N is less than  length  of  X/Y, only
+                        leading N elements are used.
+            CnstrLeft-  optional equality constraint for model value at the   left
+                        boundary (at X=0). Specify NAN (Not-a-Number)  if  you  do
+                        not need constraint on the model value at X=0 (in C++  you
+                        can pass alglib::fp_nan as parameter, in  C#  it  will  be
+                        Double.NaN).
+                        See  below,  section  "EQUALITY  CONSTRAINTS"   for   more
+                        information about constraints.
+            CnstrRight- optional equality constraint for model value at X=infinity.
+                        Specify NAN (Not-a-Number) if you do not  need  constraint
+                        on the model value (in C++  you can pass alglib::fp_nan as
+                        parameter, in  C# it will  be Double.NaN).
+                        See  below,  section  "EQUALITY  CONSTRAINTS"   for   more
+                        information about constraints.
+            Is4PL   -   whether 4PL or 5PL models are fitted
+            LambdaV -   regularization coefficient, LambdaV>=0.
+                        Set it to zero unless you know what you are doing.
+            EpsX    -   stopping condition (step size), EpsX>=0.
+                        Zero value means that small step is automatically chosen.
+                        See notes below for more information.
+            RsCnt   -   number of repeated restarts from  random  points.  4PL/5PL
+                        models are prone to problem of bad local extrema. Utilizing
+                        multiple random restarts allows  us  to  improve algorithm
+                        convergence.
+                        RsCnt>=0.
+                        Zero value means that function automatically choose  small
+                        amount of restarts (recommended).
+                        
+        OUTPUT PARAMETERS:
+            A, B, C, D- parameters of 4PL model
+            G       -   parameter of 5PL model; for Is4PL=True, G=1 is returned.
+            Rep     -   fitting report. This structure has many fields,  but  ONLY
+                        ONES LISTED BELOW ARE SET:
+                        * Rep.IterationsCount - number of iterations performed
+                        * Rep.RMSError - root-mean-square error
+                        * Rep.AvgError - average absolute error
+                        * Rep.AvgRelError - average relative error (calculated for
+                          non-zero Y-values)
+                        * Rep.MaxError - maximum absolute error
+                        * Rep.R2 - coefficient of determination,  R-squared.  This
+                          coefficient   is  calculated  as  R2=1-RSS/TSS  (in case
+                          of nonlinear  regression  there  are  multiple  ways  to
+                          define R2, each of them giving different results).
+                        
+        NOTE: for better stability B  parameter is restricted by [+-1/1000,+-1000]
+              range, and G is restricted by [1/10,10] range. It prevents algorithm
+              from making trial steps deep into the area of bad parameters.
+
+        NOTE: after  you  obtained  coefficients,  you  can  evaluate  model  with
+              LogisticCalc5() function.
+
+        NOTE: step is automatically scaled according to scale of parameters  being
+              fitted before we compare its length with EpsX. Thus,  this  function
+              can be used to fit data with very small or very large values without
+              changing EpsX.
+
+        EQUALITY CONSTRAINTS ON PARAMETERS
+
+        4PL/5PL solver supports equality constraints on model values at  the  left
+        boundary (X=0) and right  boundary  (X=infinity).  These  constraints  are
+        completely optional and you can specify both of them, only  one  -  or  no
+        constraints at all.
+
+        Parameter  CnstrLeft  contains  left  constraint (or NAN for unconstrained
+        fitting), and CnstrRight contains right  one.  For  4PL,  left  constraint
+        ALWAYS corresponds to parameter A, and right one is ALWAYS  constraint  on
+        D. That's because 4PL model is normalized in such way that B>=0.
+
+        For 5PL model things are different. Unlike  4PL  one,  5PL  model  is  NOT
+        symmetric with respect to  change  in  sign  of  B. Thus, negative B's are
+        possible, and left constraint may constrain parameter A (for positive B's)
+        - or parameter D (for negative B's). Similarly changes  meaning  of  right
+        constraint.
+
+        You do not have to decide what parameter to  constrain  -  algorithm  will
+        automatically determine correct parameters as fitting progresses. However,
+        question highlighted above is important when you interpret fitting results.
+            
+
+          -- ALGLIB PROJECT --
+             Copyright 14.02.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void logisticfit45x(double[] x,
+            double[] y,
+            int n,
+            double cnstrleft,
+            double cnstrright,
+            bool is4pl,
+            double lambdav,
+            double epsx,
+            int rscnt,
+            ref double a,
+            ref double b,
+            ref double c,
+            ref double d,
+            ref double g,
+            lsfitreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int outerit = 0;
+            int nz = 0;
+            double v = 0;
+            double[] p0 = new double[0];
+            double[] p1 = new double[0];
+            double[] p2 = new double[0];
+            double[] bndl = new double[0];
+            double[] bndu = new double[0];
+            double[] s = new double[0];
+            double[] bndl1 = new double[0];
+            double[] bndu1 = new double[0];
+            double[] bndl2 = new double[0];
+            double[] bndu2 = new double[0];
+            double[,] z = new double[0,0];
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+            minlm.minlmstate state = new minlm.minlmstate();
+            minlm.minlmreport replm = new minlm.minlmreport();
+            int maxits = 0;
+            double fbest = 0;
+            double flast = 0;
+            double scalex = 0;
+            double scaley = 0;
+            double[] bufx = new double[0];
+            double[] bufy = new double[0];
+            double fposb = 0;
+            double fnegb = 0;
+
+            x = (double[])x.Clone();
+            y = (double[])y.Clone();
+            a = 0;
+            b = 0;
+            c = 0;
+            d = 0;
+            g = 0;
+
+            alglib.ap.assert(math.isfinite(epsx), "LogisticFitX: EpsX is infinite/NAN");
+            alglib.ap.assert(math.isfinite(lambdav), "LogisticFitX: LambdaV is infinite/NAN");
+            alglib.ap.assert(math.isfinite(cnstrleft) || Double.IsNaN(cnstrleft), "LogisticFitX: CnstrLeft is NOT finite or NAN");
+            alglib.ap.assert(math.isfinite(cnstrright) || Double.IsNaN(cnstrright), "LogisticFitX: CnstrRight is NOT finite or NAN");
+            alglib.ap.assert((double)(lambdav)>=(double)(0), "LogisticFitX: negative LambdaV");
+            alglib.ap.assert(n>0, "LogisticFitX: N<=0");
+            alglib.ap.assert(rscnt>=0, "LogisticFitX: RsCnt<0");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "LogisticFitX: EpsX<0");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "LogisticFitX: Length(X)<N");
+            alglib.ap.assert(alglib.ap.len(y)>=n, "LogisticFitX: Length(Y)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "LogisticFitX: X contains infinite/NAN values");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "LogisticFitX: X contains infinite/NAN values");
+            hqrnd.hqrndseed(2211, 1033044, rs, _params);
+            clearreport(rep, _params);
+            if( (double)(epsx)==(double)(0) )
+            {
+                epsx = 1.0E-10;
+            }
+            if( rscnt==0 )
+            {
+                rscnt = 4;
+            }
+            maxits = 1000;
+            
+            //
+            // Sort points by X.
+            // Determine number of zero and non-zero values.
+            //
+            tsort.tagsortfastr(ref x, ref y, ref bufx, ref bufy, n, _params);
+            alglib.ap.assert((double)(x[0])>=(double)(0), "LogisticFitX: some X[] are negative");
+            nz = n;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(x[i])>(double)(0) )
+                {
+                    nz = i;
+                    break;
+                }
+            }
+            
+            //
+            // For NZ=N (all X[] are zero) special code is used.
+            // For NZ<N we use general-purpose code.
+            //
+            rep.iterationscount = 0;
+            if( nz==n )
+            {
+                
+                //
+                // NZ=N, degenerate problem.
+                // No need to run optimizer.
+                //
+                v = 0.0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v = v+y[i];
+                }
+                v = v/n;
+                if( math.isfinite(cnstrleft) )
+                {
+                    a = cnstrleft;
+                }
+                else
+                {
+                    a = v;
+                }
+                b = 1;
+                c = 1;
+                if( math.isfinite(cnstrright) )
+                {
+                    d = cnstrright;
+                }
+                else
+                {
+                    d = a;
+                }
+                g = 1;
+                logisticfit45errors(x, y, n, a, b, c, d, g, rep, _params);
+                return;
+            }
+            
+            //
+            // Non-degenerate problem.
+            // Determine scale of data.
+            //
+            scalex = x[nz+(n-nz)/2];
+            alglib.ap.assert((double)(scalex)>(double)(0), "LogisticFitX: internal error");
+            v = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+y[i];
+            }
+            v = v/n;
+            scaley = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                scaley = scaley+math.sqr(y[i]-v);
+            }
+            scaley = Math.Sqrt(scaley/n);
+            if( (double)(scaley)==(double)(0) )
+            {
+                scaley = 1.0;
+            }
+            s = new double[5];
+            s[0] = scaley;
+            s[1] = 0.1;
+            s[2] = scalex;
+            s[3] = scaley;
+            s[4] = 0.1;
+            p0 = new double[5];
+            p0[0] = 0;
+            p0[1] = 0;
+            p0[2] = 0;
+            p0[3] = 0;
+            p0[4] = 0;
+            bndl = new double[5];
+            bndu = new double[5];
+            bndl1 = new double[5];
+            bndu1 = new double[5];
+            bndl2 = new double[5];
+            bndu2 = new double[5];
+            minlm.minlmcreatevj(5, n+5, p0, state, _params);
+            minlm.minlmsetscale(state, s, _params);
+            minlm.minlmsetcond(state, epsx, maxits, _params);
+            minlm.minlmsetxrep(state, true, _params);
+            p1 = new double[5];
+            p2 = new double[5];
+            
+            //
+            // Is it 4PL problem?
+            //
+            if( is4pl )
+            {
+                
+                //
+                // Run outer iterations
+                //
+                a = 0;
+                b = 1;
+                c = 1;
+                d = 1;
+                g = 1;
+                fbest = math.maxrealnumber;
+                for(outerit=0; outerit<=rscnt-1; outerit++)
+                {
+                    
+                    //
+                    // Prepare initial point; use B>0
+                    //
+                    if( math.isfinite(cnstrleft) )
+                    {
+                        p1[0] = cnstrleft;
+                    }
+                    else
+                    {
+                        p1[0] = y[0]+0.15*scaley*(hqrnd.hqrnduniformr(rs, _params)-0.5);
+                    }
+                    p1[1] = 0.5+hqrnd.hqrnduniformr(rs, _params);
+                    p1[2] = x[nz+hqrnd.hqrnduniformi(rs, n-nz, _params)];
+                    if( math.isfinite(cnstrright) )
+                    {
+                        p1[3] = cnstrright;
+                    }
+                    else
+                    {
+                        p1[3] = y[n-1]+0.25*scaley*(hqrnd.hqrnduniformr(rs, _params)-0.5);
+                    }
+                    p1[4] = 1.0;
+                    
+                    //
+                    // Run optimization with tight constraints and increased regularization
+                    //
+                    if( math.isfinite(cnstrleft) )
+                    {
+                        bndl[0] = cnstrleft;
+                        bndu[0] = cnstrleft;
+                    }
+                    else
+                    {
+                        bndl[0] = Double.NegativeInfinity;
+                        bndu[0] = Double.PositiveInfinity;
+                    }
+                    bndl[1] = 0.5;
+                    bndu[1] = 2.0;
+                    bndl[2] = 0.5*scalex;
+                    bndu[2] = 2.0*scalex;
+                    if( math.isfinite(cnstrright) )
+                    {
+                        bndl[3] = cnstrright;
+                        bndu[3] = cnstrright;
+                    }
+                    else
+                    {
+                        bndl[3] = Double.NegativeInfinity;
+                        bndu[3] = Double.PositiveInfinity;
+                    }
+                    bndl[4] = 1.0;
+                    bndu[4] = 1.0;
+                    minlm.minlmsetbc(state, bndl, bndu, _params);
+                    logisticfitinternal(x, y, n, is4pl, 100*lambdav, state, replm, ref p1, ref flast, _params);
+                    rep.iterationscount = rep.iterationscount+replm.iterationscount;
+                    
+                    //
+                    // Relax constraints, run optimization one more time
+                    //
+                    bndl[1] = 0.1;
+                    bndu[1] = 10.0;
+                    bndl[2] = math.machineepsilon*scalex;
+                    bndu[2] = scalex/math.machineepsilon;
+                    minlm.minlmsetbc(state, bndl, bndu, _params);
+                    logisticfitinternal(x, y, n, is4pl, lambdav, state, replm, ref p1, ref flast, _params);
+                    rep.iterationscount = rep.iterationscount+replm.iterationscount;
+                    
+                    //
+                    // Relax constraints more, run optimization one more time
+                    //
+                    bndl[1] = 0.01;
+                    bndu[1] = 100.0;
+                    minlm.minlmsetbc(state, bndl, bndu, _params);
+                    logisticfitinternal(x, y, n, is4pl, lambdav, state, replm, ref p1, ref flast, _params);
+                    rep.iterationscount = rep.iterationscount+replm.iterationscount;
+                    
+                    //
+                    // Relax constraints ever more, run optimization one more time
+                    //
+                    bndl[1] = 0.001;
+                    bndu[1] = 1000.0;
+                    minlm.minlmsetbc(state, bndl, bndu, _params);
+                    logisticfitinternal(x, y, n, is4pl, lambdav, state, replm, ref p1, ref flast, _params);
+                    rep.iterationscount = rep.iterationscount+replm.iterationscount;
+                    
+                    //
+                    // Compare results with best value found so far.
+                    //
+                    if( (double)(flast)<(double)(fbest) )
+                    {
+                        a = p1[0];
+                        b = p1[1];
+                        c = p1[2];
+                        d = p1[3];
+                        g = p1[4];
+                        fbest = flast;
+                    }
+                }
+                logisticfit45errors(x, y, n, a, b, c, d, g, rep, _params);
+                return;
+            }
+            
+            //
+            // Well.... we have 5PL fit, and we have to test two separate branches:
+            // B>0 and B<0, because of asymmetry in the curve. First, we run optimization
+            // with tight constraints two times, in order to determine better sign for B.
+            //
+            // Run outer iterations
+            //
+            a = 0;
+            b = 1;
+            c = 1;
+            d = 1;
+            g = 1;
+            fbest = math.maxrealnumber;
+            for(outerit=0; outerit<=rscnt-1; outerit++)
+            {
+                
+                //
+                // First, we try positive B.
+                //
+                p1[0] = y[0]+0.15*scaley*(hqrnd.hqrnduniformr(rs, _params)-0.5);
+                p1[1] = 0.5+hqrnd.hqrnduniformr(rs, _params);
+                p1[2] = x[nz+hqrnd.hqrnduniformi(rs, n-nz, _params)];
+                p1[3] = y[n-1]+0.25*scaley*(hqrnd.hqrnduniformr(rs, _params)-0.5);
+                p1[4] = 1.0;
+                bndl1[0] = Double.NegativeInfinity;
+                bndu1[0] = Double.PositiveInfinity;
+                bndl1[1] = 0.5;
+                bndu1[1] = 2.0;
+                bndl1[2] = 0.5*scalex;
+                bndu1[2] = 2.0*scalex;
+                bndl1[3] = Double.NegativeInfinity;
+                bndu1[3] = Double.PositiveInfinity;
+                bndl1[4] = 0.5;
+                bndu1[4] = 2.0;
+                if( math.isfinite(cnstrleft) )
+                {
+                    p1[0] = cnstrleft;
+                    bndl1[0] = cnstrleft;
+                    bndu1[0] = cnstrleft;
+                }
+                if( math.isfinite(cnstrright) )
+                {
+                    p1[3] = cnstrright;
+                    bndl1[3] = cnstrright;
+                    bndu1[3] = cnstrright;
+                }
+                minlm.minlmsetbc(state, bndl1, bndu1, _params);
+                logisticfitinternal(x, y, n, is4pl, 100*lambdav, state, replm, ref p1, ref fposb, _params);
+                rep.iterationscount = rep.iterationscount+replm.iterationscount;
+                
+                //
+                // Second attempt - with negative B (constraints are still tight).
+                //
+                p2[0] = y[n-1]+0.15*scaley*(hqrnd.hqrnduniformr(rs, _params)-0.5);
+                p2[1] = -(0.5+hqrnd.hqrnduniformr(rs, _params));
+                p2[2] = x[nz+hqrnd.hqrnduniformi(rs, n-nz, _params)];
+                p2[3] = y[0]+0.25*scaley*(hqrnd.hqrnduniformr(rs, _params)-0.5);
+                p2[4] = 1.0;
+                bndl2[0] = Double.NegativeInfinity;
+                bndu2[0] = Double.PositiveInfinity;
+                bndl2[1] = -2.0;
+                bndu2[1] = -0.5;
+                bndl2[2] = 0.5*scalex;
+                bndu2[2] = 2.0*scalex;
+                bndl2[3] = Double.NegativeInfinity;
+                bndu2[3] = Double.PositiveInfinity;
+                bndl2[4] = 0.5;
+                bndu2[4] = 2.0;
+                if( math.isfinite(cnstrleft) )
+                {
+                    p2[3] = cnstrleft;
+                    bndl2[3] = cnstrleft;
+                    bndu2[3] = cnstrleft;
+                }
+                if( math.isfinite(cnstrright) )
+                {
+                    p2[0] = cnstrright;
+                    bndl2[0] = cnstrright;
+                    bndu2[0] = cnstrright;
+                }
+                minlm.minlmsetbc(state, bndl2, bndu2, _params);
+                logisticfitinternal(x, y, n, is4pl, 100*lambdav, state, replm, ref p2, ref fnegb, _params);
+                rep.iterationscount = rep.iterationscount+replm.iterationscount;
+                
+                //
+                // Select best version of B sign
+                //
+                if( (double)(fposb)<(double)(fnegb) )
+                {
+                    
+                    //
+                    // Prepare relaxed constraints assuming that B is positive
+                    //
+                    bndl1[1] = 0.1;
+                    bndu1[1] = 10.0;
+                    bndl1[2] = math.machineepsilon*scalex;
+                    bndu1[2] = scalex/math.machineepsilon;
+                    bndl1[4] = 0.1;
+                    bndu1[4] = 10.0;
+                    minlm.minlmsetbc(state, bndl1, bndu1, _params);
+                    logisticfitinternal(x, y, n, is4pl, lambdav, state, replm, ref p1, ref flast, _params);
+                    rep.iterationscount = rep.iterationscount+replm.iterationscount;
+                    
+                    //
+                    // Prepare stronger relaxation of constraints
+                    //
+                    bndl1[1] = 0.01;
+                    bndu1[1] = 100.0;
+                    minlm.minlmsetbc(state, bndl1, bndu1, _params);
+                    logisticfitinternal(x, y, n, is4pl, lambdav, state, replm, ref p1, ref flast, _params);
+                    rep.iterationscount = rep.iterationscount+replm.iterationscount;
+                    
+                    //
+                    // Prepare stronger relaxation of constraints
+                    //
+                    bndl1[1] = 0.001;
+                    bndu1[1] = 1000.0;
+                    minlm.minlmsetbc(state, bndl1, bndu1, _params);
+                    logisticfitinternal(x, y, n, is4pl, lambdav, state, replm, ref p1, ref flast, _params);
+                    rep.iterationscount = rep.iterationscount+replm.iterationscount;
+                    
+                    //
+                    // Compare results with best value found so far.
+                    //
+                    if( (double)(flast)<(double)(fbest) )
+                    {
+                        a = p1[0];
+                        b = p1[1];
+                        c = p1[2];
+                        d = p1[3];
+                        g = p1[4];
+                        fbest = flast;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Prepare relaxed constraints assuming that B is negative
+                    //
+                    bndl2[1] = -10.0;
+                    bndu2[1] = -0.1;
+                    bndl2[2] = math.machineepsilon*scalex;
+                    bndu2[2] = scalex/math.machineepsilon;
+                    bndl2[4] = 0.1;
+                    bndu2[4] = 10.0;
+                    minlm.minlmsetbc(state, bndl2, bndu2, _params);
+                    logisticfitinternal(x, y, n, is4pl, lambdav, state, replm, ref p2, ref flast, _params);
+                    rep.iterationscount = rep.iterationscount+replm.iterationscount;
+                    
+                    //
+                    // Prepare stronger relaxation
+                    //
+                    bndl2[1] = -100.0;
+                    bndu2[1] = -0.01;
+                    minlm.minlmsetbc(state, bndl2, bndu2, _params);
+                    logisticfitinternal(x, y, n, is4pl, lambdav, state, replm, ref p2, ref flast, _params);
+                    rep.iterationscount = rep.iterationscount+replm.iterationscount;
+                    
+                    //
+                    // Prepare stronger relaxation
+                    //
+                    bndl2[1] = -1000.0;
+                    bndu2[1] = -0.001;
+                    minlm.minlmsetbc(state, bndl2, bndu2, _params);
+                    logisticfitinternal(x, y, n, is4pl, lambdav, state, replm, ref p2, ref flast, _params);
+                    rep.iterationscount = rep.iterationscount+replm.iterationscount;
+                    
+                    //
+                    // Compare results with best value found so far.
+                    //
+                    if( (double)(flast)<(double)(fbest) )
+                    {
+                        a = p2[0];
+                        b = p2[1];
+                        c = p2[2];
+                        d = p2[3];
+                        g = p2[4];
+                        fbest = flast;
+                    }
+                }
+            }
+            logisticfit45errors(x, y, n, a, b, c, d, g, rep, _params);
+        }
+
+
+        /*************************************************************************
+        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.
+          
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "BarycentricFitFloaterHormannWC: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "BarycentricFitFloaterHormannWC: Y contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(w, n, _params), "BarycentricFitFloaterHormannWC: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(xc, k, _params), "BarycentricFitFloaterHormannWC: XC contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(yc, k, _params), "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, _params);
+                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], _params)));
+                    }
+                    wrmscur = Math.Sqrt(wrmscur/n);
+                    if( (double)(wrmscur)<(double)(wrmsbest) || rep.dbest<0 )
+                    {
+                        ratint.barycentriccopy(locb, b, _params);
+                        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).
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "BarycentricFitFloaterHormann: X contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+                                        
+        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,
+            spline1d.spline1dfitreport rep,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DFitCubicWC: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "Spline1DFitCubicWC: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(w, n, _params), "Spline1DFitCubicWC: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(xc, k, _params), "Spline1DFitCubicWC: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(yc, k, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+                                        
+        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,
+            spline1d.spline1dfitreport rep,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DFitHermiteWC: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "Spline1DFitHermiteWC: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(w, n, _params), "Spline1DFitHermiteWC: Y contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(xc, k, _params), "Spline1DFitHermiteWC: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(yc, k, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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)
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+          -- 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,
+            spline1d.spline1dfitreport rep,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DFitCubic: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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).
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+          -- 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,
+            spline1d.spline1dfitreport rep,
+            alglib.xparams _params)
+        {
+            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, _params), "Spline1DFitHermite: X contains infinite or NAN values!");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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.
+
+        IMPORTANT: if you want to perform  polynomial  fitting,  it  may  be  more
+                   convenient to use PolynomialFit() function. This function gives
+                   best  results  on  polynomial  problems  and  solves  numerical
+                   stability  issues  which  arise  when   you   fit   high-degree
+                   polynomials to your data.
+                   
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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
+                        * R2                non-adjusted coefficient of determination
+                                            (non-weighted)
+                        * 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
+                        
+        ERRORS IN PARAMETERS                
+                        
+        This  solver  also  calculates different kinds of errors in parameters and
+        fills corresponding fields of report:
+        * Rep.CovPar        covariance matrix for parameters, array[K,K].
+        * Rep.ErrPar        errors in parameters, array[K],
+                            errpar = sqrt(diag(CovPar))
+        * Rep.ErrCurve      vector of fit errors - standard deviations of empirical
+                            best-fit curve from "ideal" best-fit curve built  with
+                            infinite number of samples, array[N].
+                            errcurve = sqrt(diag(F*CovPar*F')),
+                            where F is functions matrix.
+        * Rep.Noise         vector of per-point estimates of noise, array[N]
+                    
+        NOTE:       noise in the data is estimated as follows:
+                    * for fitting without user-supplied  weights  all  points  are
+                      assumed to have same level of noise, which is estimated from
+                      the data
+                    * for fitting with user-supplied weights we assume that  noise
+                      level in I-th point is inversely proportional to Ith weight.
+                      Coefficient of proportionality is estimated from the data.
+                    
+        NOTE:       we apply small amount of regularization when we invert squared
+                    Jacobian and calculate covariance matrix. It  guarantees  that
+                    algorithm won't divide by zero  during  inversion,  but  skews
+                    error estimates a bit (fractional error is about 10^-9).
+                    
+                    However, we believe that this difference is insignificant  for
+                    all practical purposes except for the situation when you  want
+                    to compare ALGLIB results with "reference"  implementation  up
+                    to the last significant digit.
+                    
+        NOTE:       covariance matrix is estimated using  correction  for  degrees
+                    of freedom (covariances are divided by N-M instead of dividing
+                    by N).
+                                            
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, _params), "LSFitLinearW: FMatrix contains infinite or NaN values!");
+            lsfitlinearinternal(y, w, fmatrix, n, m, ref info, ref c, rep, _params);
+        }
+
+
+        /*************************************************************************
+        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.
+
+        IMPORTANT: if you want to perform  polynomial  fitting,  it  may  be  more
+                   convenient to use PolynomialFit() function. This function gives
+                   best  results  on  polynomial  problems  and  solves  numerical
+                   stability  issues  which  arise  when   you   fit   high-degree
+                   polynomials to your data.
+                   
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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:
+                        * R2                non-adjusted coefficient of determination
+                                            (non-weighted)
+                        * 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.
+                        
+        ERRORS IN PARAMETERS                
+                        
+        This  solver  also  calculates different kinds of errors in parameters and
+        fills corresponding fields of report:
+        * Rep.CovPar        covariance matrix for parameters, array[K,K].
+        * Rep.ErrPar        errors in parameters, array[K],
+                            errpar = sqrt(diag(CovPar))
+        * Rep.ErrCurve      vector of fit errors - standard deviations of empirical
+                            best-fit curve from "ideal" best-fit curve built  with
+                            infinite number of samples, array[N].
+                            errcurve = sqrt(diag(F*CovPar*F')),
+                            where F is functions matrix.
+        * Rep.Noise         vector of per-point estimates of noise, array[N]
+
+        IMPORTANT:  errors  in  parameters  are  calculated  without  taking  into
+                    account boundary/linear constraints! Presence  of  constraints
+                    changes distribution of errors, but there is no  easy  way  to
+                    account for constraints when you calculate covariance matrix.
+                    
+        NOTE:       noise in the data is estimated as follows:
+                    * for fitting without user-supplied  weights  all  points  are
+                      assumed to have same level of noise, which is estimated from
+                      the data
+                    * for fitting with user-supplied weights we assume that  noise
+                      level in I-th point is inversely proportional to Ith weight.
+                      Coefficient of proportionality is estimated from the data.
+                    
+        NOTE:       we apply small amount of regularization when we invert squared
+                    Jacobian and calculate covariance matrix. It  guarantees  that
+                    algorithm won't divide by zero  during  inversion,  but  skews
+                    error estimates a bit (fractional error is about 10^-9).
+                    
+                    However, we believe that this difference is insignificant  for
+                    all practical purposes except for the situation when you  want
+                    to compare ALGLIB results with "reference"  implementation  up
+                    to the last significant digit.
+                    
+        NOTE:       covariance matrix is estimated using  correction  for  degrees
+                    of freedom (covariances are divided by N-M instead of dividing
+                    by N).
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, _params), "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, _params), "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, _params);
+            }
+            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, _params);
+                ortfac.rmatrixlqunpackq(cmatrix, k, m, tau, m, ref q, _params);
+                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, _params))<(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, _params);
+                ablas.rmatrixgemm(n, m-k, m, 1.0, fmatrix, 0, 0, 0, q, k, 0, 1, 0.0, f2, 0, 0, _params);
+                lsfitlinearinternal(y, w, f2, n, m-k, ref info, ref tmp, rep, _params);
+                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, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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.
+
+        IMPORTANT: if you want to perform  polynomial  fitting,  it  may  be  more
+                   convenient to use PolynomialFit() function. This function gives
+                   best  results  on  polynomial  problems  and  solves  numerical
+                   stability  issues  which  arise  when   you   fit   high-degree
+                   polynomials to your data.
+                   
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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
+                        * R2                non-adjusted coefficient of determination
+                                            (non-weighted)
+                        * 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
+                        
+        ERRORS IN PARAMETERS                
+                        
+        This  solver  also  calculates different kinds of errors in parameters and
+        fills corresponding fields of report:
+        * Rep.CovPar        covariance matrix for parameters, array[K,K].
+        * Rep.ErrPar        errors in parameters, array[K],
+                            errpar = sqrt(diag(CovPar))
+        * Rep.ErrCurve      vector of fit errors - standard deviations of empirical
+                            best-fit curve from "ideal" best-fit curve built  with
+                            infinite number of samples, array[N].
+                            errcurve = sqrt(diag(F*CovPar*F')),
+                            where F is functions matrix.
+        * Rep.Noise         vector of per-point estimates of noise, array[N]
+                    
+        NOTE:       noise in the data is estimated as follows:
+                    * for fitting without user-supplied  weights  all  points  are
+                      assumed to have same level of noise, which is estimated from
+                      the data
+                    * for fitting with user-supplied weights we assume that  noise
+                      level in I-th point is inversely proportional to Ith weight.
+                      Coefficient of proportionality is estimated from the data.
+                    
+        NOTE:       we apply small amount of regularization when we invert squared
+                    Jacobian and calculate covariance matrix. It  guarantees  that
+                    algorithm won't divide by zero  during  inversion,  but  skews
+                    error estimates a bit (fractional error is about 10^-9).
+                    
+                    However, we believe that this difference is insignificant  for
+                    all practical purposes except for the situation when you  want
+                    to compare ALGLIB results with "reference"  implementation  up
+                    to the last significant digit.
+                    
+        NOTE:       covariance matrix is estimated using  correction  for  degrees
+                    of freedom (covariances are divided by N-M instead of dividing
+                    by N).
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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.
+
+        IMPORTANT: if you want to perform  polynomial  fitting,  it  may  be  more
+                   convenient to use PolynomialFit() function. This function gives
+                   best  results  on  polynomial  problems  and  solves  numerical
+                   stability  issues  which  arise  when   you   fit   high-degree
+                   polynomials to your data.
+                   
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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:
+                        * R2                non-adjusted coefficient of determination
+                                            (non-weighted)
+                        * 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.
+                        
+        ERRORS IN PARAMETERS                
+                        
+        This  solver  also  calculates different kinds of errors in parameters and
+        fills corresponding fields of report:
+        * Rep.CovPar        covariance matrix for parameters, array[K,K].
+        * Rep.ErrPar        errors in parameters, array[K],
+                            errpar = sqrt(diag(CovPar))
+        * Rep.ErrCurve      vector of fit errors - standard deviations of empirical
+                            best-fit curve from "ideal" best-fit curve built  with
+                            infinite number of samples, array[N].
+                            errcurve = sqrt(diag(F*CovPar*F')),
+                            where F is functions matrix.
+        * Rep.Noise         vector of per-point estimates of noise, array[N]
+
+        IMPORTANT:  errors  in  parameters  are  calculated  without  taking  into
+                    account boundary/linear constraints! Presence  of  constraints
+                    changes distribution of errors, but there is no  easy  way  to
+                    account for constraints when you calculate covariance matrix.
+                    
+        NOTE:       noise in the data is estimated as follows:
+                    * for fitting without user-supplied  weights  all  points  are
+                      assumed to have same level of noise, which is estimated from
+                      the data
+                    * for fitting with user-supplied weights we assume that  noise
+                      level in I-th point is inversely proportional to Ith weight.
+                      Coefficient of proportionality is estimated from the data.
+                    
+        NOTE:       we apply small amount of regularization when we invert squared
+                    Jacobian and calculate covariance matrix. It  guarantees  that
+                    algorithm won't divide by zero  during  inversion,  but  skews
+                    error estimates a bit (fractional error is about 10^-9).
+                    
+                    However, we believe that this difference is insignificant  for
+                    all practical purposes except for the situation when you  want
+                    to compare ALGLIB results with "reference"  implementation  up
+                    to the last significant digit.
+                    
+        NOTE:       covariance matrix is estimated using  correction  for  degrees
+                    of freedom (covariances are divided by N-M instead of dividing
+                    by N).
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, _params), "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, _params), "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.teststep = 0;
+            state.diffstep = diffstep;
+            state.npoints = n;
+            state.nweights = n;
+            state.wkind = 1;
+            state.m = m;
+            state.k = k;
+            lsfitsetcond(state, 0.0, 0, _params);
+            lsfitsetstpmax(state, 0.0, _params);
+            lsfitsetxrep(state, false, _params);
+            state.taskx = new double[n, m];
+            state.tasky = new double[n];
+            state.taskw = new double[n];
+            state.c = new double[k];
+            state.c0 = new double[k];
+            state.c1 = new double[k];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c0[i_] = c[i_];
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c1[i_] = c[i_];
+            }
+            state.x = new double[m];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.taskw[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;
+            state.nec = 0;
+            state.nic = 0;
+            minlm.minlmcreatev(k, n, state.c0, diffstep, state.optstate, _params);
+            lsfitclearrequestfields(state, _params);
+            state.rstate.ia = new int[6+1];
+            state.rstate.ra = new double[8+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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, _params), "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, _params), "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.teststep = 0;
+            state.diffstep = diffstep;
+            state.npoints = n;
+            state.wkind = 0;
+            state.m = m;
+            state.k = k;
+            lsfitsetcond(state, 0.0, 0, _params);
+            lsfitsetstpmax(state, 0.0, _params);
+            lsfitsetxrep(state, false, _params);
+            state.taskx = new double[n, m];
+            state.tasky = new double[n];
+            state.c = new double[k];
+            state.c0 = new double[k];
+            state.c1 = new double[k];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c0[i_] = c[i_];
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c1[i_] = c[i_];
+            }
+            state.x = new double[m];
+            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;
+            state.nec = 0;
+            state.nic = 0;
+            minlm.minlmcreatev(k, n, state.c0, diffstep, state.optstate, _params);
+            lsfitclearrequestfields(state, _params);
+            state.rstate.ia = new int[6+1];
+            state.rstate.ra = new double[8+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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, _params), "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, _params), "LSFitCreateWFG: X contains infinite or NaN values!");
+            state.teststep = 0;
+            state.diffstep = 0;
+            state.npoints = n;
+            state.nweights = n;
+            state.wkind = 1;
+            state.m = m;
+            state.k = k;
+            lsfitsetcond(state, 0.0, 0, _params);
+            lsfitsetstpmax(state, 0.0, _params);
+            lsfitsetxrep(state, false, _params);
+            state.taskx = new double[n, m];
+            state.tasky = new double[n];
+            state.taskw = new double[n];
+            state.c = new double[k];
+            state.c0 = new double[k];
+            state.c1 = new double[k];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c0[i_] = c[i_];
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c1[i_] = c[i_];
+            }
+            state.x = new double[m];
+            state.g = new double[k];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.taskw[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;
+            state.nec = 0;
+            state.nic = 0;
+            if( cheapfg )
+            {
+                minlm.minlmcreatevgj(k, n, state.c0, state.optstate, _params);
+            }
+            else
+            {
+                minlm.minlmcreatevj(k, n, state.c0, state.optstate, _params);
+            }
+            lsfitclearrequestfields(state, _params);
+            state.rstate.ia = new int[6+1];
+            state.rstate.ra = new double[8+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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, _params), "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, _params), "LSFitCreateFG: X contains infinite or NaN values!");
+            state.teststep = 0;
+            state.diffstep = 0;
+            state.npoints = n;
+            state.wkind = 0;
+            state.m = m;
+            state.k = k;
+            lsfitsetcond(state, 0.0, 0, _params);
+            lsfitsetstpmax(state, 0.0, _params);
+            lsfitsetxrep(state, false, _params);
+            state.taskx = new double[n, m];
+            state.tasky = new double[n];
+            state.c = new double[k];
+            state.c0 = new double[k];
+            state.c1 = new double[k];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c0[i_] = c[i_];
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c1[i_] = c[i_];
+            }
+            state.x = new double[m];
+            state.g = new double[k];
+            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;
+            state.nec = 0;
+            state.nic = 0;
+            if( cheapfg )
+            {
+                minlm.minlmcreatevgj(k, n, state.c0, state.optstate, _params);
+            }
+            else
+            {
+                minlm.minlmcreatevj(k, n, state.c0, state.optstate, _params);
+            }
+            lsfitclearrequestfields(state, _params);
+            state.rstate.ia = new int[6+1];
+            state.rstate.ra = new double[8+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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, _params), "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, _params), "LSFitCreateWFGH: X contains infinite or NaN values!");
+            state.teststep = 0;
+            state.diffstep = 0;
+            state.npoints = n;
+            state.nweights = n;
+            state.wkind = 1;
+            state.m = m;
+            state.k = k;
+            lsfitsetcond(state, 0.0, 0, _params);
+            lsfitsetstpmax(state, 0.0, _params);
+            lsfitsetxrep(state, false, _params);
+            state.taskx = new double[n, m];
+            state.tasky = new double[n];
+            state.taskw = new double[n];
+            state.c = new double[k];
+            state.c0 = new double[k];
+            state.c1 = new double[k];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c0[i_] = c[i_];
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c1[i_] = c[i_];
+            }
+            state.h = new double[k, k];
+            state.x = new double[m];
+            state.g = new double[k];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.taskw[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;
+            state.nec = 0;
+            state.nic = 0;
+            minlm.minlmcreatefgh(k, state.c0, state.optstate, _params);
+            lsfitclearrequestfields(state, _params);
+            state.rstate.ia = new int[6+1];
+            state.rstate.ra = new double[8+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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, _params), "LSFitCreateFGH: X contains infinite or NaN values!");
+            state.teststep = 0;
+            state.diffstep = 0;
+            state.npoints = n;
+            state.wkind = 0;
+            state.m = m;
+            state.k = k;
+            lsfitsetcond(state, 0.0, 0, _params);
+            lsfitsetstpmax(state, 0.0, _params);
+            lsfitsetxrep(state, false, _params);
+            state.taskx = new double[n, m];
+            state.tasky = new double[n];
+            state.c = new double[k];
+            state.c0 = new double[k];
+            state.c1 = new double[k];
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c0[i_] = c[i_];
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c1[i_] = c[i_];
+            }
+            state.h = new double[k, k];
+            state.x = new double[m];
+            state.g = new double[k];
+            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;
+            state.nec = 0;
+            state.nic = 0;
+            minlm.minlmcreatefgh(k, state.c0, state.optstate, _params);
+            lsfitclearrequestfields(state, _params);
+            state.rstate.ia = new int[6+1];
+            state.rstate.ra = new double[8+1];
+            state.rstate.stage = -1;
+        }
+
+
+        /*************************************************************************
+        Stopping conditions for nonlinear least squares fitting.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            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 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 epsx,
+            int maxits,
+            alglib.xparams _params)
+        {
+            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.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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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] = Math.Abs(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,
+            alglib.xparams _params)
+        {
+            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];
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets linear constraints for underlying optimizer
+
+        Linear constraints are inactive by default (after initial creation).
+        They are preserved until explicitly turned off with another SetLC() call.
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            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
+
+        IMPORTANT: if you have linear constraints, it is strongly  recommended  to
+                   set scale of variables with lsfitsetscale(). QP solver which is
+                   used to calculate linearly constrained steps heavily relies  on
+                   good scaling of input problems.
+                   
+        NOTE: linear  (non-box)  constraints  are  satisfied only approximately  -
+              there  always  exists some violation due  to  numerical  errors  and
+              algorithmic limitations.
+
+        NOTE: general linear constraints  add  significant  overhead  to  solution
+              process. Although solver performs roughly same amount of  iterations
+              (when compared  with  similar  box-only  constrained  problem), each
+              iteration   now    involves  solution  of  linearly  constrained  QP
+              subproblem, which requires ~3-5 times more Cholesky  decompositions.
+              Thus, if you can reformulate your problem in such way  this  it  has
+              only box constraints, it may be beneficial to do so.
+
+          -- ALGLIB --
+             Copyright 29.04.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitsetlc(lsfitstate state,
+            double[,] c,
+            int[] ct,
+            int k,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+            int i_ = 0;
+
+            n = state.k;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(k>=0, "LSFitSetLC: K<0");
+            alglib.ap.assert(alglib.ap.cols(c)>=n+1 || k==0, "LSFitSetLC: Cols(C)<N+1");
+            alglib.ap.assert(alglib.ap.rows(c)>=k, "LSFitSetLC: Rows(C)<K");
+            alglib.ap.assert(alglib.ap.len(ct)>=k, "LSFitSetLC: Length(CT)<K");
+            alglib.ap.assert(apserv.apservisfinitematrix(c, k, n+1, _params), "LSFitSetLC: C contains infinite or NaN values!");
+            
+            //
+            // Handle zero K
+            //
+            if( k==0 )
+            {
+                state.nec = 0;
+                state.nic = 0;
+                return;
+            }
+            
+            //
+            // Equality constraints are stored first, in the upper
+            // NEC rows of State.CLEIC matrix. Inequality constraints
+            // are stored in the next NIC rows.
+            //
+            // NOTE: we convert inequality constraints to the form
+            // A*x<=b before copying them.
+            //
+            apserv.rmatrixsetlengthatleast(ref state.cleic, k, n+1, _params);
+            state.nec = 0;
+            state.nic = 0;
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]==0 )
+                {
+                    for(i_=0; i_<=n;i_++)
+                    {
+                        state.cleic[state.nec,i_] = c[i,i_];
+                    }
+                    state.nec = state.nec+1;
+                }
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]!=0 )
+                {
+                    if( ct[i]>0 )
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec+state.nic,i_] = -c[i,i_];
+                        }
+                    }
+                    else
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec+state.nic,i_] = c[i,i_];
+                        }
+                    }
+                    state.nic = state.nic+1;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            double lx = 0;
+            double lf = 0;
+            double ld = 0;
+            double rx = 0;
+            double rf = 0;
+            double rd = 0;
+            int n = 0;
+            int m = 0;
+            int k = 0;
+            double v = 0;
+            double vv = 0;
+            double relcnt = 0;
+            int i = 0;
+            int j = 0;
+            int j1 = 0;
+            int info = 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];
+                j1 = state.rstate.ia[5];
+                info = state.rstate.ia[6];
+                lx = state.rstate.ra[0];
+                lf = state.rstate.ra[1];
+                ld = state.rstate.ra[2];
+                rx = state.rstate.ra[3];
+                rf = state.rstate.ra[4];
+                rd = state.rstate.ra[5];
+                v = state.rstate.ra[6];
+                vv = state.rstate.ra[7];
+                relcnt = state.rstate.ra[8];
+            }
+            else
+            {
+                n = 359;
+                m = -58;
+                k = -919;
+                i = -909;
+                j = 81;
+                j1 = 255;
+                info = 74;
+                lx = -788;
+                lf = 809;
+                ld = 205;
+                rx = -838;
+                rf = 939;
+                rd = -526;
+                v = 763;
+                vv = -541;
+                relcnt = -698;
+            }
+            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
+            //
+            
+            //
+            // Init
+            //
+            if( state.wkind==1 )
+            {
+                alglib.ap.assert(state.npoints==state.nweights, "LSFitFit: number of points is not equal to the number of weights");
+            }
+            state.repvaridx = -1;
+            n = state.npoints;
+            m = state.m;
+            k = state.k;
+            apserv.ivectorsetlengthatleast(ref state.tmpct, state.nec+state.nic, _params);
+            for(i=0; i<=state.nec-1; i++)
+            {
+                state.tmpct[i] = 0;
+            }
+            for(i=0; i<=state.nic-1; i++)
+            {
+                state.tmpct[state.nec+i] = -1;
+            }
+            minlm.minlmsetcond(state.optstate, state.epsx, state.maxits, _params);
+            minlm.minlmsetstpmax(state.optstate, state.stpmax, _params);
+            minlm.minlmsetxrep(state.optstate, state.xrep, _params);
+            minlm.minlmsetscale(state.optstate, state.s, _params);
+            minlm.minlmsetbc(state.optstate, state.bndl, state.bndu, _params);
+            minlm.minlmsetlc(state.optstate, state.cleic, state.tmpct, state.nec+state.nic, _params);
+            
+            //
+            //  Check that user-supplied gradient is correct
+            //
+            lsfitclearrequestfields(state, _params);
+            if( !((double)(state.teststep)>(double)(0) && state.optalgo==1) )
+            {
+                goto lbl_14;
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                state.c[i] = state.c0[i];
+                if( math.isfinite(state.bndl[i]) )
+                {
+                    state.c[i] = Math.Max(state.c[i], state.bndl[i]);
+                }
+                if( math.isfinite(state.bndu[i]) )
+                {
+                    state.c[i] = Math.Min(state.c[i], state.bndu[i]);
+                }
+            }
+            state.needfg = true;
+            i = 0;
+        lbl_16:
+            if( i>k-1 )
+            {
+                goto lbl_18;
+            }
+            alglib.ap.assert((double)(state.bndl[i])<=(double)(state.c[i]) && (double)(state.c[i])<=(double)(state.bndu[i]), "LSFitIteration: internal error(State.C is out of bounds)");
+            v = state.c[i];
+            j = 0;
+        lbl_19:
+            if( j>n-1 )
+            {
+                goto lbl_21;
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.x[i_] = state.taskx[j,i_];
+            }
+            state.c[i] = v-state.teststep*state.s[i];
+            if( math.isfinite(state.bndl[i]) )
+            {
+                state.c[i] = Math.Max(state.c[i], state.bndl[i]);
+            }
+            lx = state.c[i];
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            lf = state.f;
+            ld = state.g[i];
+            state.c[i] = v+state.teststep*state.s[i];
+            if( math.isfinite(state.bndu[i]) )
+            {
+                state.c[i] = Math.Min(state.c[i], state.bndu[i]);
+            }
+            rx = state.c[i];
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            rf = state.f;
+            rd = state.g[i];
+            state.c[i] = (lx+rx)/2;
+            if( math.isfinite(state.bndl[i]) )
+            {
+                state.c[i] = Math.Max(state.c[i], state.bndl[i]);
+            }
+            if( math.isfinite(state.bndu[i]) )
+            {
+                state.c[i] = Math.Min(state.c[i], state.bndu[i]);
+            }
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.c[i] = v;
+            if( !optserv.derivativecheck(lf, ld, rf, rd, state.f, state.g[i], rx-lx, _params) )
+            {
+                state.repvaridx = i;
+                state.repterminationtype = -7;
+                result = false;
+                return result;
+            }
+            j = j+1;
+            goto lbl_19;
+        lbl_21:
+            i = i+1;
+            goto lbl_16;
+        lbl_18:
+            state.needfg = false;
+        lbl_14:
+            
+            //
+            // Fill WCur by weights:
+            // * for WKind=0 unit weights are chosen
+            // * for WKind=1 we use user-supplied weights stored in State.TaskW
+            //
+            apserv.rvectorsetlengthatleast(ref state.wcur, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.wcur[i] = 1.0;
+                if( state.wkind==1 )
+                {
+                    state.wcur[i] = state.taskw[i];
+                }
+            }
+            
+            //
+            // Optimize
+            //
+        lbl_22:
+            if( !minlm.minlmiteration(state.optstate, _params) )
+            {
+                goto lbl_23;
+            }
+            if( !state.optstate.needfi )
+            {
+                goto lbl_24;
+            }
+            
+            //
+            // calculate f[] = wi*(f(xi,c)-yi)
+            //
+            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, _params);
+            state.needf = true;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.needf = false;
+            vv = state.wcur[i];
+            state.optstate.fi[i] = vv*(state.f-state.tasky[i]);
+            i = i+1;
+            goto lbl_26;
+        lbl_28:
+            goto lbl_22;
+        lbl_24:
+            if( !state.optstate.needf )
+            {
+                goto lbl_29;
+            }
+            
+            //
+            // calculate F = sum (wi*(f(xi,c)-yi))^2
+            //
+            state.optstate.f = 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, _params);
+            state.needf = true;
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.needf = false;
+            vv = state.wcur[i];
+            state.optstate.f = state.optstate.f+math.sqr(vv*(state.f-state.tasky[i]));
+            i = i+1;
+            goto lbl_31;
+        lbl_33:
+            goto lbl_22;
+        lbl_29:
+            if( !state.optstate.needfg )
+            {
+                goto lbl_34;
+            }
+            
+            //
+            // calculate F/gradF
+            //
+            state.optstate.f = 0;
+            for(i=0; i<=k-1; i++)
+            {
+                state.optstate.g[i] = 0;
+            }
+            i = 0;
+        lbl_36:
+            if( i>n-1 )
+            {
+                goto lbl_38;
+            }
+            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, _params);
+            state.needfg = true;
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.needfg = false;
+            vv = state.wcur[i];
+            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_36;
+        lbl_38:
+            goto lbl_22;
+        lbl_34:
+            if( !state.optstate.needfij )
+            {
+                goto lbl_39;
+            }
+            
+            //
+            // calculate Fi/jac(Fi)
+            //
+            i = 0;
+        lbl_41:
+            if( i>n-1 )
+            {
+                goto lbl_43;
+            }
+            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, _params);
+            state.needfg = true;
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            state.needfg = false;
+            vv = state.wcur[i];
+            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_41;
+        lbl_43:
+            goto lbl_22;
+        lbl_39:
+            if( !state.optstate.needfgh )
+            {
+                goto lbl_44;
+            }
+            
+            //
+            // 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_46:
+            if( i>n-1 )
+            {
+                goto lbl_48;
+            }
+            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, _params);
+            state.needfgh = true;
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.needfgh = false;
+            vv = state.wcur[i];
+            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_46;
+        lbl_48:
+            goto lbl_22;
+        lbl_44:
+            if( !state.optstate.xupdated )
+            {
+                goto lbl_49;
+            }
+            
+            //
+            // Report new iteration
+            //
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = state.optstate.x[i_];
+            }
+            state.f = state.optstate.f;
+            lsfitclearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 8;
+            goto lbl_rcomm;
+        lbl_8:
+            state.xupdated = false;
+            goto lbl_22;
+        lbl_49:
+            goto lbl_22;
+        lbl_23:
+            
+            //
+            // Extract results
+            //
+            // NOTE: reverse communication protocol used by this unit does NOT
+            //       allow us to reallocate State.C[] array. Thus, we extract
+            //       results to the temporary variable in order to avoid possible
+            //       reallocation.
+            //
+            minlm.minlmresults(state.optstate, ref state.c1, state.optrep, _params);
+            state.repterminationtype = state.optrep.terminationtype;
+            state.repiterationscount = state.optrep.iterationscount;
+            
+            //
+            // calculate errors
+            //
+            if( state.repterminationtype<=0 )
+            {
+                goto lbl_51;
+            }
+            
+            //
+            // Calculate RMS/Avg/Max/... errors
+            //
+            state.reprmserror = 0;
+            state.repwrmserror = 0;
+            state.repavgerror = 0;
+            state.repavgrelerror = 0;
+            state.repmaxerror = 0;
+            relcnt = 0;
+            i = 0;
+        lbl_53:
+            if( i>n-1 )
+            {
+                goto lbl_55;
+            }
+            for(i_=0; i_<=k-1;i_++)
+            {
+                state.c[i_] = state.c1[i_];
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.x[i_] = state.taskx[i,i_];
+            }
+            state.pointindex = i;
+            lsfitclearrequestfields(state, _params);
+            state.needf = true;
+            state.rstate.stage = 9;
+            goto lbl_rcomm;
+        lbl_9:
+            state.needf = false;
+            v = state.f;
+            vv = state.wcur[i];
+            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_53;
+        lbl_55:
+            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;
+            }
+            
+            //
+            // Calculate covariance matrix
+            //
+            apserv.rmatrixsetlengthatleast(ref state.tmpjac, n, k, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmpf, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmp, k, _params);
+            if( (double)(state.diffstep)<=(double)(0) )
+            {
+                goto lbl_56;
+            }
+            
+            //
+            // Compute Jacobian by means of numerical differentiation
+            //
+            lsfitclearrequestfields(state, _params);
+            state.needf = true;
+            i = 0;
+        lbl_58:
+            if( i>n-1 )
+            {
+                goto lbl_60;
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.x[i_] = state.taskx[i,i_];
+            }
+            state.pointindex = i;
+            state.rstate.stage = 10;
+            goto lbl_rcomm;
+        lbl_10:
+            state.tmpf[i] = state.f;
+            j = 0;
+        lbl_61:
+            if( j>k-1 )
+            {
+                goto lbl_63;
+            }
+            v = state.c[j];
+            lx = v-state.diffstep*state.s[j];
+            state.c[j] = lx;
+            if( math.isfinite(state.bndl[j]) )
+            {
+                state.c[j] = Math.Max(state.c[j], state.bndl[j]);
+            }
+            state.rstate.stage = 11;
+            goto lbl_rcomm;
+        lbl_11:
+            lf = state.f;
+            rx = v+state.diffstep*state.s[j];
+            state.c[j] = rx;
+            if( math.isfinite(state.bndu[j]) )
+            {
+                state.c[j] = Math.Min(state.c[j], state.bndu[j]);
+            }
+            state.rstate.stage = 12;
+            goto lbl_rcomm;
+        lbl_12:
+            rf = state.f;
+            state.c[j] = v;
+            if( (double)(rx)!=(double)(lx) )
+            {
+                state.tmpjac[i,j] = (rf-lf)/(rx-lx);
+            }
+            else
+            {
+                state.tmpjac[i,j] = 0;
+            }
+            j = j+1;
+            goto lbl_61;
+        lbl_63:
+            i = i+1;
+            goto lbl_58;
+        lbl_60:
+            state.needf = false;
+            goto lbl_57;
+        lbl_56:
+            
+            //
+            // Jacobian is calculated with user-provided analytic gradient
+            //
+            lsfitclearrequestfields(state, _params);
+            state.needfg = true;
+            i = 0;
+        lbl_64:
+            if( i>n-1 )
+            {
+                goto lbl_66;
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.x[i_] = state.taskx[i,i_];
+            }
+            state.pointindex = i;
+            state.rstate.stage = 13;
+            goto lbl_rcomm;
+        lbl_13:
+            state.tmpf[i] = state.f;
+            for(j=0; j<=k-1; j++)
+            {
+                state.tmpjac[i,j] = state.g[j];
+            }
+            i = i+1;
+            goto lbl_64;
+        lbl_66:
+            state.needfg = false;
+        lbl_57:
+            for(i=0; i<=k-1; i++)
+            {
+                state.tmp[i] = 0.0;
+            }
+            estimateerrors(state.tmpjac, state.tmpf, state.tasky, state.wcur, state.tmp, state.s, n, k, state.rep, ref state.tmpjacw, 0, _params);
+        lbl_51:
+            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.ia[5] = j1;
+            state.rstate.ia[6] = info;
+            state.rstate.ra[0] = lx;
+            state.rstate.ra[1] = lf;
+            state.rstate.ra[2] = ld;
+            state.rstate.ra[3] = rx;
+            state.rstate.ra[4] = rf;
+            state.rstate.ra[5] = rd;
+            state.rstate.ra[6] = v;
+            state.rstate.ra[7] = vv;
+            state.rstate.ra[8] = relcnt;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Nonlinear least squares fitting results.
+
+        Called after return from LSFitFit().
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            Info    -   completion code:
+                            * -8    optimizer   detected  NAN/INF  in  the  target
+                                    function and/or gradient
+                            * -7    gradient verification failed.
+                                    See LSFitSetGradientCheck() for more information.
+                            * -3    inconsistent constraints
+                            *  2    relative step is no more than EpsX.
+                            *  5    MaxIts steps was taken
+                            *  7    stopping conditions are too stringent,
+                                    further improvement is impossible
+            C       -   array[0..K-1], solution
+            Rep     -   optimization report. On success following fields are set:
+                        * R2                non-adjusted coefficient of determination
+                                            (non-weighted)
+                        * 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).
+                        
+        ERRORS IN PARAMETERS                
+                        
+        This  solver  also  calculates different kinds of errors in parameters and
+        fills corresponding fields of report:
+        * Rep.CovPar        covariance matrix for parameters, array[K,K].
+        * Rep.ErrPar        errors in parameters, array[K],
+                            errpar = sqrt(diag(CovPar))
+        * Rep.ErrCurve      vector of fit errors - standard deviations of empirical
+                            best-fit curve from "ideal" best-fit curve built  with
+                            infinite number of samples, array[N].
+                            errcurve = sqrt(diag(J*CovPar*J')),
+                            where J is Jacobian matrix.
+        * Rep.Noise         vector of per-point estimates of noise, array[N]
+
+        IMPORTANT:  errors  in  parameters  are  calculated  without  taking  into
+                    account boundary/linear constraints! Presence  of  constraints
+                    changes distribution of errors, but there is no  easy  way  to
+                    account for constraints when you calculate covariance matrix.
+                    
+        NOTE:       noise in the data is estimated as follows:
+                    * for fitting without user-supplied  weights  all  points  are
+                      assumed to have same level of noise, which is estimated from
+                      the data
+                    * for fitting with user-supplied weights we assume that  noise
+                      level in I-th point is inversely proportional to Ith weight.
+                      Coefficient of proportionality is estimated from the data.
+                    
+        NOTE:       we apply small amount of regularization when we invert squared
+                    Jacobian and calculate covariance matrix. It  guarantees  that
+                    algorithm won't divide by zero  during  inversion,  but  skews
+                    error estimates a bit (fractional error is about 10^-9).
+                    
+                    However, we believe that this difference is insignificant  for
+                    all practical purposes except for the situation when you  want
+                    to compare ALGLIB results with "reference"  implementation  up
+                    to the last significant digit.
+                    
+        NOTE:       covariance matrix is estimated using  correction  for  degrees
+                    of freedom (covariances are divided by N-M instead of dividing
+                    by N).
+
+          -- ALGLIB --
+             Copyright 17.08.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitresults(lsfitstate state,
+            ref int info,
+            ref double[] c,
+            lsfitreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int i_ = 0;
+
+            info = 0;
+            c = new double[0];
+
+            clearreport(rep, _params);
+            info = state.repterminationtype;
+            rep.varidx = state.repvaridx;
+            if( info>0 )
+            {
+                c = new double[state.k];
+                for(i_=0; i_<=state.k-1;i_++)
+                {
+                    c[i_] = state.c1[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;
+                rep.covpar = new double[state.k, state.k];
+                rep.errpar = new double[state.k];
+                rep.errcurve = new double[state.npoints];
+                rep.noise = new double[state.npoints];
+                rep.r2 = state.rep.r2;
+                for(i=0; i<=state.k-1; i++)
+                {
+                    for(j=0; j<=state.k-1; j++)
+                    {
+                        rep.covpar[i,j] = state.rep.covpar[i,j];
+                    }
+                    rep.errpar[i] = state.rep.errpar[i];
+                }
+                for(i=0; i<=state.npoints-1; i++)
+                {
+                    rep.errcurve[i] = state.rep.errcurve[i];
+                    rep.noise[i] = state.rep.noise[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This  subroutine  turns  on  verification  of  the  user-supplied analytic
+        gradient:
+        * user calls this subroutine before fitting begins
+        * LSFitFit() is called
+        * prior to actual fitting, for  each  point  in  data  set  X_i  and  each
+          component  of  parameters  being  fited C_j algorithm performs following
+          steps:
+          * two trial steps are made to C_j-TestStep*S[j] and C_j+TestStep*S[j],
+            where C_j is j-th parameter and S[j] is a scale of j-th parameter
+          * if needed, steps are bounded with respect to constraints on C[]
+          * F(X_i|C) is evaluated at these trial points
+          * we perform one more evaluation in the middle point of the interval
+          * we  build  cubic  model using function values and derivatives at trial
+            points and we compare its prediction with actual value in  the  middle
+            point
+          * in case difference between prediction and actual value is higher  than
+            some predetermined threshold, algorithm stops with completion code -7;
+            Rep.VarIdx is set to index of the parameter with incorrect derivative.
+        * after verification is over, algorithm proceeds to the actual optimization.
+
+        NOTE 1: verification needs N*K (points count * parameters count)  gradient
+                evaluations. It is very costly and you should use it only for  low
+                dimensional  problems,  when  you  want  to  be  sure  that you've
+                correctly calculated analytic derivatives. You should not  use  it
+                in the production code  (unless  you  want  to  check  derivatives
+                provided by some third party).
+
+        NOTE 2: you  should  carefully  choose  TestStep. Value which is too large
+                (so large that function behaviour is significantly non-cubic) will
+                lead to false alarms. You may use  different  step  for  different
+                parameters by means of setting scale with LSFitSetScale().
+
+        NOTE 3: this function may lead to false positives. In case it reports that
+                I-th  derivative was calculated incorrectly, you may decrease test
+                step  and  try  one  more  time  - maybe your function changes too
+                sharply  and  your  step  is  too  large for such rapidly chanding
+                function.
+
+        NOTE 4: this function works only for optimizers created with LSFitCreateWFG()
+                or LSFitCreateFG() constructors.
+                
+        INPUT PARAMETERS:
+            State       -   structure used to store algorithm state
+            TestStep    -   verification step:
+                            * TestStep=0 turns verification off
+                            * TestStep>0 activates verification
+
+          -- ALGLIB --
+             Copyright 15.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lsfitsetgradientcheck(lsfitstate state,
+            double teststep,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(teststep), "LSFitSetGradientCheck: TestStep contains NaN or Infinite");
+            alglib.ap.assert((double)(teststep)>=(double)(0), "LSFitSetGradientCheck: invalid argument TestStep(TestStep<0)");
+            state.teststep = teststep;
+        }
+
+
+        /*************************************************************************
+        This function analyzes section of curve for processing by RDP algorithm:
+        given set of points X,Y with indexes [I0,I1] it returns point with
+        worst deviation from linear model (non-parametric version which sees curve
+        as Y(x)).
+
+        Input parameters:
+            X, Y        -   SORTED arrays.
+            I0,I1       -   interval (boundaries included) to process
+            Eps         -   desired precision
+            
+        OUTPUT PARAMETERS:
+            WorstIdx    -   index of worst point
+            WorstError  -   error at worst point
+            
+        NOTE: this function guarantees that it returns exactly zero for a section
+              with less than 3 points.
+
+          -- ALGLIB PROJECT --
+             Copyright 02.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rdpanalyzesection(double[] x,
+            double[] y,
+            int i0,
+            int i1,
+            ref int worstidx,
+            ref double worsterror,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double xleft = 0;
+            double xright = 0;
+            double vx = 0;
+            double ve = 0;
+            double a = 0;
+            double b = 0;
+
+            worstidx = 0;
+            worsterror = 0;
+
+            xleft = x[i0];
+            xright = x[i1];
+            if( i1-i0+1<3 || (double)(xright)==(double)(xleft) )
+            {
+                worstidx = i0;
+                worsterror = 0.0;
+                return;
+            }
+            a = (y[i1]-y[i0])/(xright-xleft);
+            b = (y[i0]*xright-y[i1]*xleft)/(xright-xleft);
+            worstidx = -1;
+            worsterror = 0;
+            for(i=i0+1; i<=i1-1; i++)
+            {
+                vx = x[i];
+                ve = Math.Abs(a*vx+b-y[i]);
+                if( ((double)(vx)>(double)(xleft) && (double)(vx)<(double)(xright)) && (double)(ve)>(double)(worsterror) )
+                {
+                    worsterror = ve;
+                    worstidx = i;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Recursive splitting of interval [I0,I1] (right boundary included) with RDP
+        algorithm (non-parametric version which sees curve as Y(x)).
+
+        Input parameters:
+            X, Y        -   SORTED arrays.
+            I0,I1       -   interval (boundaries included) to process
+            Eps         -   desired precision
+            XOut,YOut   -   preallocated output arrays large enough to store result;
+                            XOut[0..1], YOut[0..1] contain first and last points of
+                            curve
+            NOut        -   must contain 2 on input
+            
+        OUTPUT PARAMETERS:
+            XOut, YOut  -   curve generated by RDP algorithm, UNSORTED
+            NOut        -   number of points in curve
+
+          -- ALGLIB PROJECT --
+             Copyright 02.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rdprecursive(double[] x,
+            double[] y,
+            int i0,
+            int i1,
+            double eps,
+            double[] xout,
+            double[] yout,
+            ref int nout,
+            alglib.xparams _params)
+        {
+            int worstidx = 0;
+            double worsterror = 0;
+
+            alglib.ap.assert((double)(eps)>(double)(0), "RDPRecursive: internal error, Eps<0");
+            rdpanalyzesection(x, y, i0, i1, ref worstidx, ref worsterror, _params);
+            if( (double)(worsterror)<=(double)(eps) )
+            {
+                return;
+            }
+            xout[nout] = x[worstidx];
+            yout[nout] = y[worstidx];
+            nout = nout+1;
+            if( worstidx-i0<i1-worstidx )
+            {
+                rdprecursive(x, y, i0, worstidx, eps, xout, yout, ref nout, _params);
+                rdprecursive(x, y, worstidx, i1, eps, xout, yout, ref nout, _params);
+            }
+            else
+            {
+                rdprecursive(x, y, worstidx, i1, eps, xout, yout, ref nout, _params);
+                rdprecursive(x, y, i0, worstidx, eps, xout, yout, ref nout, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Internal 4PL/5PL fitting function.
+
+        Accepts X, Y and already initialized and prepared MinLMState structure.
+        On input P1 contains initial guess, on output it contains solution.  FLast
+        stores function value at P1.
+        *************************************************************************/
+        private static void logisticfitinternal(double[] x,
+            double[] y,
+            int n,
+            bool is4pl,
+            double lambdav,
+            minlm.minlmstate state,
+            minlm.minlmreport replm,
+            ref double[] p1,
+            ref double flast,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double ta = 0;
+            double tb = 0;
+            double tc = 0;
+            double td = 0;
+            double tg = 0;
+            double vp0 = 0;
+            double vp1 = 0;
+
+            flast = 0;
+
+            minlm.minlmrestartfrom(state, p1, _params);
+            while( minlm.minlmiteration(state, _params) )
+            {
+                ta = state.x[0];
+                tb = state.x[1];
+                tc = state.x[2];
+                td = state.x[3];
+                tg = state.x[4];
+                if( state.xupdated )
+                {
+                    
+                    //
+                    // Save best function value obtained so far.
+                    //
+                    flast = state.f;
+                    continue;
+                }
+                if( state.needfi || state.needfij )
+                {
+                    
+                    //
+                    // Function vector and Jacobian
+                    //
+                    for(i=0; i<=n-1; i++)
+                    {
+                        alglib.ap.assert((double)(x[i])>=(double)(0), "LogisticFitInternal: integrity error");
+                        
+                        //
+                        // Handle zero X
+                        //
+                        if( (double)(x[i])==(double)(0) )
+                        {
+                            if( (double)(tb)>=(double)(0) )
+                            {
+                                
+                                //
+                                // Positive or zero TB, limit X^TB subject to X->+0 is equal to zero.
+                                //
+                                state.fi[i] = ta-y[i];
+                                if( state.needfij )
+                                {
+                                    state.j[i,0] = 1;
+                                    state.j[i,1] = 0;
+                                    state.j[i,2] = 0;
+                                    state.j[i,3] = 0;
+                                    state.j[i,4] = 0;
+                                }
+                            }
+                            else
+                            {
+                                
+                                //
+                                // Negative TB, limit X^TB subject to X->+0 is equal to +INF.
+                                //
+                                state.fi[i] = td-y[i];
+                                if( state.needfij )
+                                {
+                                    state.j[i,0] = 0;
+                                    state.j[i,1] = 0;
+                                    state.j[i,2] = 0;
+                                    state.j[i,3] = 1;
+                                    state.j[i,4] = 0;
+                                }
+                            }
+                            continue;
+                        }
+                        
+                        //
+                        // Positive X.
+                        // Prepare VP0/VP1, it may become infinite or nearly overflow in some rare cases,
+                        // handle these cases
+                        //
+                        vp0 = Math.Pow(x[i]/tc, tb);
+                        if( is4pl )
+                        {
+                            vp1 = 1+vp0;
+                        }
+                        else
+                        {
+                            vp1 = Math.Pow(1+vp0, tg);
+                        }
+                        if( (!math.isfinite(vp1) || (double)(vp0)>(double)(1.0E50)) || (double)(vp1)>(double)(1.0E50) )
+                        {
+                            
+                            //
+                            // VP0/VP1 are not finite, assume that it is +INF or -INF
+                            //
+                            state.fi[i] = td-y[i];
+                            if( state.needfij )
+                            {
+                                state.j[i,0] = 0;
+                                state.j[i,1] = 0;
+                                state.j[i,2] = 0;
+                                state.j[i,3] = 1;
+                                state.j[i,4] = 0;
+                            }
+                            continue;
+                        }
+                        
+                        //
+                        // VP0/VP1 are finite, normal processing
+                        //
+                        if( is4pl )
+                        {
+                            state.fi[i] = td+(ta-td)/vp1-y[i];
+                            if( state.needfij )
+                            {
+                                state.j[i,0] = 1/vp1;
+                                state.j[i,1] = -((ta-td)*vp0*Math.Log(x[i]/tc)/math.sqr(vp1));
+                                state.j[i,2] = (ta-td)*(tb/tc)*vp0/math.sqr(vp1);
+                                state.j[i,3] = 1-1/vp1;
+                                state.j[i,4] = 0;
+                            }
+                        }
+                        else
+                        {
+                            state.fi[i] = td+(ta-td)/vp1-y[i];
+                            if( state.needfij )
+                            {
+                                state.j[i,0] = 1/vp1;
+                                state.j[i,1] = (ta-td)*-tg*Math.Pow(1+vp0, -tg-1)*vp0*Math.Log(x[i]/tc);
+                                state.j[i,2] = (ta-td)*-tg*Math.Pow(1+vp0, -tg-1)*vp0*-(tb/tc);
+                                state.j[i,3] = 1-1/vp1;
+                                state.j[i,4] = -((ta-td)/vp1*Math.Log(1+vp0));
+                            }
+                        }
+                    }
+                    
+                    //
+                    // Add regularizer
+                    //
+                    for(i=0; i<=4; i++)
+                    {
+                        state.fi[n+i] = lambdav*state.x[i];
+                        if( state.needfij )
+                        {
+                            for(j=0; j<=4; j++)
+                            {
+                                state.j[n+i,j] = 0.0;
+                            }
+                            state.j[n+i,i] = lambdav;
+                        }
+                    }
+                    
+                    //
+                    // Done
+                    //
+                    continue;
+                }
+                alglib.ap.assert(false, "LogisticFitX: internal error");
+            }
+            minlm.minlmresultsbuf(state, ref p1, replm, _params);
+            alglib.ap.assert(replm.terminationtype>0, "LogisticFitX: internal error");
+        }
+
+
+        /*************************************************************************
+        Calculate errors for 4PL/5PL fit.
+        Leaves other fields of Rep unchanged, so caller should properly initialize
+        it with ClearRep() call.
+
+          -- ALGLIB PROJECT --
+             Copyright 28.04.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void logisticfit45errors(double[] x,
+            double[] y,
+            int n,
+            double a,
+            double b,
+            double c,
+            double d,
+            double g,
+            lsfitreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+            double v = 0;
+            double rss = 0;
+            double tss = 0;
+            double meany = 0;
+
+            
+            //
+            // Calculate errors
+            //
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.avgrelerror = 0;
+            rep.maxerror = 0;
+            k = 0;
+            rss = 0.0;
+            tss = 0.0;
+            meany = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                meany = meany+y[i];
+            }
+            meany = meany/n;
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Calculate residual from regression
+                //
+                if( (double)(x[i])>(double)(0) )
+                {
+                    v = d+(a-d)/Math.Pow(1.0+Math.Pow(x[i]/c, b), g)-y[i];
+                }
+                else
+                {
+                    if( (double)(b)>=(double)(0) )
+                    {
+                        v = a-y[i];
+                    }
+                    else
+                    {
+                        v = d-y[i];
+                    }
+                }
+                
+                //
+                // Update RSS (residual sum of squares) and TSS (total sum of squares)
+                // which are used to calculate coefficient of determination.
+                //
+                // NOTE: we use formula R2 = 1-RSS/TSS because it has nice property of
+                //       being equal to 0.0 if and only if model perfectly fits data.
+                //
+                //       When we fit nonlinear models, there are exist multiple ways of
+                //       determining R2, each of them giving different results. Formula
+                //       above is the most intuitive one.
+                //
+                rss = rss+v*v;
+                tss = tss+math.sqr(y[i]-meany);
+                
+                //
+                // Update errors
+                //
+                rep.rmserror = rep.rmserror+math.sqr(v);
+                rep.avgerror = rep.avgerror+Math.Abs(v);
+                if( (double)(y[i])!=(double)(0) )
+                {
+                    rep.avgrelerror = rep.avgrelerror+Math.Abs(v/y[i]);
+                    k = k+1;
+                }
+                rep.maxerror = Math.Max(rep.maxerror, Math.Abs(v));
+            }
+            rep.rmserror = Math.Sqrt(rep.rmserror/n);
+            rep.avgerror = rep.avgerror/n;
+            if( k>0 )
+            {
+                rep.avgrelerror = rep.avgrelerror/k;
+            }
+            rep.r2 = 1.0-rss/tss;
+        }
+
+
+        /*************************************************************************
+        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,
+            spline1d.spline1dfitreport rep,
+            alglib.xparams _params)
+        {
+            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
+            //
+            intfitserv.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, _params);
+            
+            //
+            // 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, _params);
+                }
+                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, _params);
+                }
+                
+                //
+                // values at X[], XC[]
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    fmatrix[i,j] = spline1d.spline1dcalc(s2, x[i], _params);
+                }
+                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, _params);
+                    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, _params);
+            }
+            else
+            {
+                
+                //
+                // no constraints, no regularization needed
+                //
+                lsfitlinearwc(y, w, fmatrix, cmatrix, n, m, k, ref info, ref tmp, lrep, _params);
+            }
+            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, _params);
+            }
+            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, _params);
+            }
+            spline1d.spline1dlintransx(s, 2/(xb-xa), -((xa+xb)/(xb-xa)), _params);
+            spline1d.spline1dlintransy(s, sb-sa, sa, _params);
+            
+            //
+            // 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], _params)-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,
+            alglib.xparams _params)
+        {
+            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];
+            double[] nzeros = new double[0];
+            double[] s = 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];
+
+            clearreport(rep, _params);
+            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, _params);
+                ortfac.rmatrixlqunpackq(ft, n, m, tau, n, ref q, _params);
+                ortfac.rmatrixlqunpackl(ft, n, m, ref l, _params);
+                lsfitlinearinternal(b, wmod, l, n, n, ref info, ref tmp, rep, _params);
+                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, _params);
+            ortfac.rmatrixqrunpackq(ft, n, m, tau, m, ref q, _params);
+            ortfac.rmatrixqrunpackr(ft, n, m, ref r, _params);
+            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, _params);
+            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, _params) )
+                {
+                    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;
+            }
+            nzeros = new double[n];
+            s = new double[m];
+            for(i=0; i<=m-1; i++)
+            {
+                s[i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    s[j] = s[j]+math.sqr(fmatrix[i,j]);
+                }
+                nzeros[i] = 0;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                if( (double)(s[i])!=(double)(0) )
+                {
+                    s[i] = Math.Sqrt(1/s[i]);
+                }
+                else
+                {
+                    s[i] = 1;
+                }
+            }
+            estimateerrors(fmatrix, nzeros, y, w, c, s, n, m, rep, ref r, 1, _params);
+        }
+
+
+        /*************************************************************************
+        Internal subroutine
+        *************************************************************************/
+        private static void lsfitclearrequestfields(lsfitstate state,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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];
+
+            clearreport(rep, _params);
+            
+            //
+            // 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, _params);
+            }
+            else
+            {
+                
+                //
+                // no constraints, no regularization needed
+                //
+                lsfitlinearwc(y, w, fmatrix, cmatrix, n, m, 0, ref info, ref c, rep, _params);
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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
+            //
+            intfitserv.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, _params);
+            
+            //
+            // 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, _params);
+            mx = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                barycentriccalcbasis(b2, x[i], ref sbf, _params);
+                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, _params);
+                    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, _params);
+                        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, _params);
+            }
+            else
+            {
+                
+                //
+                // no constraints, no regularization needed
+                //
+                lsfitlinearwc(y, w, fmatrix, cmatrix, n, m, k, ref info, ref tmp, lrep, _params);
+            }
+            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, _params);
+            ratint.barycentriclintransx(b, 2/(xb-xa), -((xa+xb)/(xb-xa)), _params);
+            ratint.barycentriclintransy(b, sb-sa, sa, _params);
+            
+            //
+            // 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], _params)-yoriginal[i])/Math.Abs(yoriginal[i]);
+                    relcnt = relcnt+1;
+                }
+            }
+            if( relcnt!=0 )
+            {
+                rep.avgrelerror = rep.avgrelerror/relcnt;
+            }
+        }
+
+
+        private static void clearreport(lsfitreport rep,
+            alglib.xparams _params)
+        {
+            rep.taskrcond = 0;
+            rep.iterationscount = 0;
+            rep.varidx = -1;
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.avgrelerror = 0;
+            rep.maxerror = 0;
+            rep.wrmserror = 0;
+            rep.r2 = 0;
+            rep.covpar = new double[0, 0];
+            rep.errpar = new double[0];
+            rep.errcurve = new double[0];
+            rep.noise = new double[0];
+        }
+
+
+        /*************************************************************************
+        This internal function estimates covariance matrix and other error-related
+        information for linear/nonlinear least squares model.
+
+        It has a bit awkward interface, but it can be used  for  both  linear  and
+        nonlinear problems.
+
+        INPUT PARAMETERS:
+            F1  -   array[0..N-1,0..K-1]:
+                    * for linear problems - matrix of function values
+                    * for nonlinear problems - Jacobian matrix
+            F0  -   array[0..N-1]:
+                    * for linear problems - must be filled with zeros
+                    * for nonlinear problems - must store values of function being
+                      fitted
+            Y   -   array[0..N-1]:
+                    * for linear and nonlinear problems - must store target values
+            W   -   weights, array[0..N-1]:
+                    * for linear and nonlinear problems - weights
+            X   -   array[0..K-1]:
+                    * for linear and nonlinear problems - current solution
+            S   -   array[0..K-1]:
+                    * its components should be strictly positive
+                    * squared inverse of this diagonal matrix is used as damping
+                      factor for covariance matrix (linear and nonlinear problems)
+                    * for nonlinear problems, when scale of the variables is usually
+                      explicitly given by user, you may use scale vector for this
+                      parameter
+                    * for linear problems you may set this parameter to
+                      S=sqrt(1/diag(F'*F))
+                    * this parameter is automatically rescaled by this function,
+                      only relative magnitudes of its components (with respect to
+                      each other) matter.
+            N   -   number of points, N>0.
+            K   -   number of dimensions
+            Rep -   structure which is used to store results
+            Z   -   additional matrix which, depending on ZKind, may contain some
+                    information used to accelerate calculations - or just can be
+                    temporary buffer:
+                    * for ZKind=0       Z contains no information, just temporary
+                                        buffer which can be resized and used as needed
+                    * for ZKind=1       Z contains triangular matrix from QR
+                                        decomposition of W*F1. This matrix can be used
+                                        to speedup calculation of covariance matrix.
+                                        It should not be changed by algorithm.
+            ZKind-  contents of Z
+
+        OUTPUT PARAMETERS:
+
+        * Rep.CovPar        covariance matrix for parameters, array[K,K].
+        * Rep.ErrPar        errors in parameters, array[K],
+                            errpar = sqrt(diag(CovPar))
+        * Rep.ErrCurve      vector of fit errors - standard deviations of empirical
+                            best-fit curve from "ideal" best-fit curve built  with
+                            infinite number of samples, array[N].
+                            errcurve = sqrt(diag(J*CovPar*J')),
+                            where J is Jacobian matrix.
+        * Rep.Noise         vector of per-point estimates of noise, array[N]
+        * Rep.R2            coefficient of determination (non-weighted)
+
+        Other fields of Rep are not changed.
+
+        IMPORTANT:  errors  in  parameters  are  calculated  without  taking  into
+                    account boundary/linear constraints! Presence  of  constraints
+                    changes distribution of errors, but there is no  easy  way  to
+                    account for constraints when you calculate covariance matrix.
+                    
+        NOTE:       noise in the data is estimated as follows:
+                    * for fitting without user-supplied  weights  all  points  are
+                      assumed to have same level of noise, which is estimated from
+                      the data
+                    * for fitting with user-supplied weights we assume that  noise
+                      level in I-th point is inversely proportional to Ith weight.
+                      Coefficient of proportionality is estimated from the data.
+                    
+        NOTE:       we apply small amount of regularization when we invert squared
+                    Jacobian and calculate covariance matrix. It  guarantees  that
+                    algorithm won't divide by zero  during  inversion,  but  skews
+                    error estimates a bit (fractional error is about 10^-9).
+                    
+                    However, we believe that this difference is insignificant  for
+                    all practical purposes except for the situation when you  want
+                    to compare ALGLIB results with "reference"  implementation  up
+                    to the last significant digit.
+
+          -- ALGLIB PROJECT --
+             Copyright 10.12.2009 by Bochkanov Sergey
+        *************************************************************************/
+        private static void estimateerrors(double[,] f1,
+            double[] f0,
+            double[] y,
+            double[] w,
+            double[] x,
+            double[] s,
+            int n,
+            int k,
+            lsfitreport rep,
+            ref double[,] z,
+            int zkind,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int j1 = 0;
+            double v = 0;
+            double noisec = 0;
+            int info = 0;
+            matinv.matinvreport invrep = new matinv.matinvreport();
+            int nzcnt = 0;
+            double avg = 0;
+            double rss = 0;
+            double tss = 0;
+            double sz = 0;
+            double ss = 0;
+            int i_ = 0;
+
+            s = (double[])s.Clone();
+
+            
+            //
+            // Compute NZCnt - count of non-zero weights
+            //
+            nzcnt = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(w[i])!=(double)(0) )
+                {
+                    nzcnt = nzcnt+1;
+                }
+            }
+            
+            //
+            // Compute R2
+            //
+            if( nzcnt>0 )
+            {
+                avg = 0.0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (double)(w[i])!=(double)(0) )
+                    {
+                        avg = avg+y[i];
+                    }
+                }
+                avg = avg/nzcnt;
+                rss = 0.0;
+                tss = 0.0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (double)(w[i])!=(double)(0) )
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=k-1;i_++)
+                        {
+                            v += f1[i,i_]*x[i_];
+                        }
+                        v = v+f0[i];
+                        rss = rss+math.sqr(v-y[i]);
+                        tss = tss+math.sqr(y[i]-avg);
+                    }
+                }
+                if( (double)(tss)!=(double)(0) )
+                {
+                    rep.r2 = Math.Max(1.0-rss/tss, 0.0);
+                }
+                else
+                {
+                    rep.r2 = 1.0;
+                }
+            }
+            else
+            {
+                rep.r2 = 0;
+            }
+            
+            //
+            // Compute estimate of proportionality between noise in the data and weights:
+            //     NoiseC = mean(per-point-noise*per-point-weight)
+            // Noise level (standard deviation) at each point is equal to NoiseC/W[I].
+            //
+            if( nzcnt>k )
+            {
+                noisec = 0.0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (double)(w[i])!=(double)(0) )
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=k-1;i_++)
+                        {
+                            v += f1[i,i_]*x[i_];
+                        }
+                        v = v+f0[i];
+                        noisec = noisec+math.sqr((v-y[i])*w[i]);
+                    }
+                }
+                noisec = Math.Sqrt(noisec/(nzcnt-k));
+            }
+            else
+            {
+                noisec = 0.0;
+            }
+            
+            //
+            // Two branches on noise level:
+            // * NoiseC>0   normal situation
+            // * NoiseC=0   degenerate case CovPar is filled by zeros
+            //
+            apserv.rmatrixsetlengthatleast(ref rep.covpar, k, k, _params);
+            if( (double)(noisec)>(double)(0) )
+            {
+                
+                //
+                // Normal situation: non-zero noise level
+                //
+                alglib.ap.assert(zkind==0 || zkind==1, "LSFit: internal error in EstimateErrors() function");
+                if( zkind==0 )
+                {
+                    
+                    //
+                    // Z contains no additional information which can be used to speed up
+                    // calculations. We have to calculate covariance matrix on our own:
+                    // * Compute scaled Jacobian N*J, where N[i,i]=WCur[I]/NoiseC, store in Z
+                    // * Compute Z'*Z, store in CovPar
+                    // * Apply moderate regularization to CovPar and compute matrix inverse.
+                    //   In case inverse failed, increase regularization parameter and try
+                    //   again.
+                    //
+                    apserv.rmatrixsetlengthatleast(ref z, n, k, _params);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        v = w[i]/noisec;
+                        for(i_=0; i_<=k-1;i_++)
+                        {
+                            z[i,i_] = v*f1[i,i_];
+                        }
+                    }
+                    
+                    //
+                    // Convert S to automatically scaled damped matrix:
+                    // * calculate SZ - sum of diagonal elements of Z'*Z
+                    // * calculate SS - sum of diagonal elements of S^(-2)
+                    // * overwrite S by (SZ/SS)*S^(-2)
+                    // * now S has approximately same magnitude as giagonal of Z'*Z
+                    //
+                    sz = 0;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=0; j<=k-1; j++)
+                        {
+                            sz = sz+z[i,j]*z[i,j];
+                        }
+                    }
+                    if( (double)(sz)==(double)(0) )
+                    {
+                        sz = 1;
+                    }
+                    ss = 0;
+                    for(j=0; j<=k-1; j++)
+                    {
+                        ss = ss+1/math.sqr(s[j]);
+                    }
+                    for(j=0; j<=k-1; j++)
+                    {
+                        s[j] = sz/ss/math.sqr(s[j]);
+                    }
+                    
+                    //
+                    // Calculate damped inverse inv(Z'*Z+S).
+                    // We increase damping factor V until Z'*Z become well-conditioned.
+                    //
+                    v = 1.0E3*math.machineepsilon;
+                    do
+                    {
+                        ablas.rmatrixsyrk(k, n, 1.0, z, 0, 0, 2, 0.0, rep.covpar, 0, 0, true, _params);
+                        for(i=0; i<=k-1; i++)
+                        {
+                            rep.covpar[i,i] = rep.covpar[i,i]+v*s[i];
+                        }
+                        matinv.spdmatrixinverse(ref rep.covpar, k, true, ref info, invrep, _params);
+                        v = 10*v;
+                    }
+                    while( info<=0 );
+                    for(i=0; i<=k-1; i++)
+                    {
+                        for(j=i+1; j<=k-1; j++)
+                        {
+                            rep.covpar[j,i] = rep.covpar[i,j];
+                        }
+                    }
+                }
+                if( zkind==1 )
+                {
+                    
+                    //
+                    // We can reuse additional information:
+                    // * Z contains R matrix from QR decomposition of W*F1 
+                    // * After multiplication by 1/NoiseC we get Z_mod = N*F1, where diag(N)=w[i]/NoiseC
+                    // * Such triangular Z_mod is a Cholesky factor from decomposition of J'*N'*N*J.
+                    //   Thus, we can calculate covariance matrix as inverse of the matrix given by
+                    //   its Cholesky decomposition. It allow us to avoid time-consuming calculation
+                    //   of J'*N'*N*J in CovPar - complexity is reduced from O(N*K^2) to O(K^3), which
+                    //   is quite good because K is usually orders of magnitude smaller than N.
+                    //
+                    // First, convert S to automatically scaled damped matrix:
+                    // * calculate SZ - sum of magnitudes of diagonal elements of Z/NoiseC
+                    // * calculate SS - sum of diagonal elements of S^(-1)
+                    // * overwrite S by (SZ/SS)*S^(-1)
+                    // * now S has approximately same magnitude as giagonal of Z'*Z
+                    //
+                    sz = 0;
+                    for(j=0; j<=k-1; j++)
+                    {
+                        sz = sz+Math.Abs(z[j,j]/noisec);
+                    }
+                    if( (double)(sz)==(double)(0) )
+                    {
+                        sz = 1;
+                    }
+                    ss = 0;
+                    for(j=0; j<=k-1; j++)
+                    {
+                        ss = ss+1/s[j];
+                    }
+                    for(j=0; j<=k-1; j++)
+                    {
+                        s[j] = sz/ss/s[j];
+                    }
+                    
+                    //
+                    // Calculate damped inverse of inv((Z+v*S)'*(Z+v*S))
+                    // We increase damping factor V until matrix become well-conditioned.
+                    //
+                    v = 1.0E3*math.machineepsilon;
+                    do
+                    {
+                        for(i=0; i<=k-1; i++)
+                        {
+                            for(j=i; j<=k-1; j++)
+                            {
+                                rep.covpar[i,j] = z[i,j]/noisec;
+                            }
+                            rep.covpar[i,i] = rep.covpar[i,i]+v*s[i];
+                        }
+                        matinv.spdmatrixcholeskyinverse(ref rep.covpar, k, true, ref info, invrep, _params);
+                        v = 10*v;
+                    }
+                    while( info<=0 );
+                    for(i=0; i<=k-1; i++)
+                    {
+                        for(j=i+1; j<=k-1; j++)
+                        {
+                            rep.covpar[j,i] = rep.covpar[i,j];
+                        }
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Degenerate situation: zero noise level, covariance matrix is zero.
+                //
+                for(i=0; i<=k-1; i++)
+                {
+                    for(j=0; j<=k-1; j++)
+                    {
+                        rep.covpar[j,i] = 0;
+                    }
+                }
+            }
+            
+            //
+            // Estimate erorrs in parameters, curve and per-point noise
+            //
+            apserv.rvectorsetlengthatleast(ref rep.errpar, k, _params);
+            apserv.rvectorsetlengthatleast(ref rep.errcurve, n, _params);
+            apserv.rvectorsetlengthatleast(ref rep.noise, n, _params);
+            for(i=0; i<=k-1; i++)
+            {
+                rep.errpar[i] = Math.Sqrt(rep.covpar[i,i]);
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // ErrCurve[I] is sqrt(P[i,i]) where P=J*CovPar*J'
+                //
+                v = 0.0;
+                for(j=0; j<=k-1; j++)
+                {
+                    for(j1=0; j1<=k-1; j1++)
+                    {
+                        v = v+f1[i,j]*rep.covpar[j,j1]*f1[i,j1];
+                    }
+                }
+                rep.errcurve[i] = Math.Sqrt(v);
+                
+                //
+                // Noise[i] is filled using weights and current estimate of noise level
+                //
+                if( (double)(w[i])!=(double)(0) )
+                {
+                    rep.noise[i] = noisec/w[i];
+                }
+                else
+                {
+                    rep.noise[i] = 0;
+                }
+            }
+        }
+
+
+    }
+    public class rbfv2
+    {
+        /*************************************************************************
+        Buffer object which is used to perform nearest neighbor  requests  in  the
+        multithreaded mode (multiple threads working with same KD-tree object).
+
+        This object should be created with KDTreeCreateBuffer().
+        *************************************************************************/
+        public class rbfv2calcbuffer : apobject
+        {
+            public double[] x;
+            public double[] curboxmin;
+            public double[] curboxmax;
+            public double curdist2;
+            public double[] x123;
+            public double[] y123;
+            public rbfv2calcbuffer()
+            {
+                init();
+            }
+            public override void init()
+            {
+                x = new double[0];
+                curboxmin = new double[0];
+                curboxmax = new double[0];
+                x123 = new double[0];
+                y123 = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                rbfv2calcbuffer _result = new rbfv2calcbuffer();
+                _result.x = (double[])x.Clone();
+                _result.curboxmin = (double[])curboxmin.Clone();
+                _result.curboxmax = (double[])curboxmax.Clone();
+                _result.curdist2 = curdist2;
+                _result.x123 = (double[])x123.Clone();
+                _result.y123 = (double[])y123.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        RBF model.
+
+        Never try to work with fields of this object directly - always use  ALGLIB
+        functions to use this object.
+        *************************************************************************/
+        public class rbfv2model : apobject
+        {
+            public int ny;
+            public int nx;
+            public int bf;
+            public int nh;
+            public double[] ri;
+            public double[] s;
+            public int[] kdroots;
+            public int[] kdnodes;
+            public double[] kdsplits;
+            public double[] kdboxmin;
+            public double[] kdboxmax;
+            public double[] cw;
+            public double[,] v;
+            public double lambdareg;
+            public int maxits;
+            public double supportr;
+            public int basisfunction;
+            public rbfv2calcbuffer calcbuf;
+            public rbfv2model()
+            {
+                init();
+            }
+            public override void init()
+            {
+                ri = new double[0];
+                s = new double[0];
+                kdroots = new int[0];
+                kdnodes = new int[0];
+                kdsplits = new double[0];
+                kdboxmin = new double[0];
+                kdboxmax = new double[0];
+                cw = new double[0];
+                v = new double[0,0];
+                calcbuf = new rbfv2calcbuffer();
+            }
+            public override alglib.apobject make_copy()
+            {
+                rbfv2model _result = new rbfv2model();
+                _result.ny = ny;
+                _result.nx = nx;
+                _result.bf = bf;
+                _result.nh = nh;
+                _result.ri = (double[])ri.Clone();
+                _result.s = (double[])s.Clone();
+                _result.kdroots = (int[])kdroots.Clone();
+                _result.kdnodes = (int[])kdnodes.Clone();
+                _result.kdsplits = (double[])kdsplits.Clone();
+                _result.kdboxmin = (double[])kdboxmin.Clone();
+                _result.kdboxmax = (double[])kdboxmax.Clone();
+                _result.cw = (double[])cw.Clone();
+                _result.v = (double[,])v.Clone();
+                _result.lambdareg = lambdareg;
+                _result.maxits = maxits;
+                _result.supportr = supportr;
+                _result.basisfunction = basisfunction;
+                _result.calcbuf = (rbfv2calcbuffer)calcbuf.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Internal buffer for GridCalc3
+        *************************************************************************/
+        public class rbfv2gridcalcbuffer : apobject
+        {
+            public rbfv2calcbuffer calcbuf;
+            public double[] cx;
+            public double[] rx;
+            public double[] ry;
+            public double[] tx;
+            public double[] ty;
+            public bool[] rf;
+            public rbfv2gridcalcbuffer()
+            {
+                init();
+            }
+            public override void init()
+            {
+                calcbuf = new rbfv2calcbuffer();
+                cx = new double[0];
+                rx = new double[0];
+                ry = new double[0];
+                tx = new double[0];
+                ty = new double[0];
+                rf = new bool[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                rbfv2gridcalcbuffer _result = new rbfv2gridcalcbuffer();
+                _result.calcbuf = (rbfv2calcbuffer)calcbuf.make_copy();
+                _result.cx = (double[])cx.Clone();
+                _result.rx = (double[])rx.Clone();
+                _result.ry = (double[])ry.Clone();
+                _result.tx = (double[])tx.Clone();
+                _result.ty = (double[])ty.Clone();
+                _result.rf = (bool[])rf.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        RBF solution report:
+        * TerminationType   -   termination type, positive values - success,
+                                non-positive - failure.
+        *************************************************************************/
+        public class rbfv2report : apobject
+        {
+            public int terminationtype;
+            public double maxerror;
+            public double rmserror;
+            public rbfv2report()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                rbfv2report _result = new rbfv2report();
+                _result.terminationtype = terminationtype;
+                _result.maxerror = maxerror;
+                _result.rmserror = rmserror;
+                return _result;
+            }
+        };
+
+
+
+
+        public const double defaultlambdareg = 1.0E-6;
+        public const double defaultsupportr = 0.10;
+        public const int defaultmaxits = 400;
+        public const int defaultbf = 1;
+        public const int maxnodesize = 6;
+        public const double complexitymultiplier = 100.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).
+
+        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)
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfv2create(int nx,
+            int ny,
+            rbfv2model s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(nx>=1, "RBFCreate: NX<1");
+            alglib.ap.assert(ny>=1, "RBFCreate: NY<1");
+            
+            //
+            // Serializable parameters
+            //
+            s.nx = nx;
+            s.ny = ny;
+            s.bf = 0;
+            s.nh = 0;
+            s.v = new double[ny, nx+1];
+            for(i=0; i<=ny-1; i++)
+            {
+                for(j=0; j<=nx; j++)
+                {
+                    s.v[i,j] = 0;
+                }
+            }
+            
+            //
+            // Non-serializable parameters
+            //
+            s.lambdareg = defaultlambdareg;
+            s.maxits = defaultmaxits;
+            s.supportr = defaultsupportr;
+            s.basisfunction = defaultbf;
+        }
+
+
+        /*************************************************************************
+        This function creates buffer  structure  which  can  be  used  to  perform
+        parallel  RBF  model  evaluations  (with  one  RBF  model  instance  being
+        used from multiple threads, as long as  different  threads  use  different
+        instances of buffer).
+
+        This buffer object can be used with  rbftscalcbuf()  function  (here  "ts"
+        stands for "thread-safe", "buf" is a suffix which denotes  function  which
+        reuses previously allocated output space).
+
+        How to use it:
+        * create RBF model structure with rbfcreate()
+        * load data, tune parameters
+        * call rbfbuildmodel()
+        * call rbfcreatecalcbuffer(), once per thread working with RBF model  (you
+          should call this function only AFTER call to rbfbuildmodel(), see  below
+          for more information)
+        * call rbftscalcbuf() from different threads,  with  each  thread  working
+          with its own copy of buffer object.
+
+        INPUT PARAMETERS
+            S           -   RBF model
+
+        OUTPUT PARAMETERS
+            Buf         -   external buffer.
+            
+            
+        IMPORTANT: buffer object should be used only with  RBF model object  which
+                   was used to initialize buffer. Any attempt to use buffer   with
+                   different object is dangerous - you may  get  memory  violation
+                   error because sizes of internal arrays do not fit to dimensions
+                   of RBF structure.
+                   
+        IMPORTANT: you  should  call  this function only for model which was built
+                   with rbfbuildmodel() function, after successful  invocation  of
+                   rbfbuildmodel().  Sizes   of   some   internal  structures  are
+                   determined only after model is built, so buffer object  created
+                   before model  construction  stage  will  be  useless  (and  any
+                   attempt to use it will result in exception).
+
+          -- ALGLIB --
+             Copyright 02.04.2016 by Sergey Bochkanov
+        *************************************************************************/
+        public static void rbfv2createcalcbuffer(rbfv2model s,
+            rbfv2calcbuffer buf,
+            alglib.xparams _params)
+        {
+            allocatecalcbuffer(s, buf, _params);
+        }
+
+
+        /*************************************************************************
+        This   function  builds hierarchical RBF model.
+
+        INPUT PARAMETERS:
+            X       -   array[N,S.NX], X-values
+            Y       -   array[N,S.NY], Y-values
+            ScaleVec-   array[S.NX], vector of per-dimension scales
+            N       -   points count
+            ATerm   -   linear term type, 1 for linear, 2 for constant, 3 for zero.
+            NH      -   hierarchy height
+            RBase   -   base RBF radius
+            BF      -   basis function type: 0 for Gaussian, 1 for compact
+            LambdaNS-   non-smoothness penalty coefficient. Exactly zero value means
+                        that no penalty is applied, and even system matrix does not
+                        contain penalty-related rows. Value of 1 means
+            S       -   RBF model, initialized by RBFCreate() call.
+            progress10000- variable used for progress reports, it is regularly set
+                        to the current progress multiplied by 10000, in order to
+                        get value in [0,10000] range. The rationale for such scaling
+                        is that it allows us to use integer type to store progress,
+                        which has less potential for non-atomic corruption on unprotected
+                        reads from another threads.
+                        You can read this variable from some other thread to get
+                        estimate of the current progress.
+                        Initial value of this variable is ignored, it is written by
+                        this function, but not read.
+            terminationrequest - variable used for termination requests; its initial
+                        value must be False, and you can set it to True from some
+                        other thread. This routine regularly checks this variable
+                        and will terminate model construction shortly upon discovering
+                        that termination was requested.
+            
+        OUTPUT PARAMETERS:
+            S       -   updated model (for rep.terminationtype>0, unchanged otherwise)
+            Rep     -   report:
+                        * Rep.TerminationType:
+                          * -5 - non-distinct basis function centers were detected,
+                                 interpolation aborted
+                          * -4 - nonconvergence of the internal SVD solver
+                          *  1 - successful termination
+                          *  8 terminated by user via rbfrequesttermination()
+                        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 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfv2buildhierarchical(double[,] x,
+            double[,] y,
+            int n,
+            double[] scalevec,
+            int aterm,
+            int nh,
+            double rbase,
+            double lambdans,
+            rbfv2model s,
+            ref int progress10000,
+            ref bool terminationrequest,
+            rbfv2report rep,
+            alglib.xparams _params)
+        {
+            int nx = 0;
+            int ny = 0;
+            int bf = 0;
+            double[,] rhs = new double[0,0];
+            double[,] residualy = new double[0,0];
+            double[,] v = new double[0,0];
+            int rowsperpoint = 0;
+            int[] hidx = new int[0];
+            double[] xr = new double[0];
+            double[] ri = new double[0];
+            int[] kdroots = new int[0];
+            int[] kdnodes = new int[0];
+            double[] kdsplits = new double[0];
+            double[] kdboxmin = new double[0];
+            double[] kdboxmax = new double[0];
+            double[] cw = new double[0];
+            int[] cwrange = new int[0];
+            double[,] curxy = new double[0,0];
+            int curn = 0;
+            int nbasis = 0;
+            nearestneighbor.kdtree curtree = new nearestneighbor.kdtree();
+            nearestneighbor.kdtree globaltree = new nearestneighbor.kdtree();
+            double[] x0 = new double[0];
+            double[] x1 = new double[0];
+            int[] tags = new int[0];
+            double[] dist = new double[0];
+            int[] nncnt = new int[0];
+            int[] rowsizes = new int[0];
+            double[] diagata = new double[0];
+            double[] prec = new double[0];
+            double[] tmpx = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int k2 = 0;
+            int levelidx = 0;
+            int offsi = 0;
+            int offsj = 0;
+            double val = 0;
+            double criticalr = 0;
+            int cnt = 0;
+            double avgdiagata = 0;
+            double[] avgrowsize = new double[0];
+            double sumrowsize = 0;
+            double rprogress = 0;
+            int maxits = 0;
+            linlsqr.linlsqrstate linstate = new linlsqr.linlsqrstate();
+            linlsqr.linlsqrreport lsqrrep = new linlsqr.linlsqrreport();
+            sparse.sparsematrix sparseacrs = new sparse.sparsematrix();
+            double[] densew1 = new double[0];
+            double[] denseb1 = new double[0];
+            rbfv2calcbuffer calcbuf = new rbfv2calcbuffer();
+            double[] vr2 = new double[0];
+            int[] voffs = new int[0];
+            int[] rowindexes = new int[0];
+            double[] rowvals = new double[0];
+            double penalty = 0;
+
+            alglib.ap.assert(s.nx>0, "RBFV2BuildHierarchical: incorrect NX");
+            alglib.ap.assert(s.ny>0, "RBFV2BuildHierarchical: incorrect NY");
+            alglib.ap.assert((double)(lambdans)>=(double)(0), "RBFV2BuildHierarchical: incorrect LambdaNS");
+            for(j=0; j<=s.nx-1; j++)
+            {
+                alglib.ap.assert((double)(scalevec[j])>(double)(0), "RBFV2BuildHierarchical: incorrect ScaleVec");
+            }
+            nx = s.nx;
+            ny = s.ny;
+            bf = s.basisfunction;
+            alglib.ap.assert(bf==0 || bf==1, "RBFV2BuildHierarchical: incorrect BF");
+            
+            //
+            // Clean up communication and report fields
+            //
+            progress10000 = 0;
+            rep.maxerror = 0;
+            rep.rmserror = 0;
+            
+            //
+            // Quick exit when we have no points
+            //
+            if( n==0 )
+            {
+                zerofill(s, nx, ny, bf, _params);
+                rep.terminationtype = 1;
+                progress10000 = 10000;
+                return;
+            }
+            
+            //
+            // 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[n, ny];
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=ny-1; j++)
+                {
+                    residualy[i,j] = y[i,j];
+                }
+            }
+            if( !rbfv2buildlinearmodel(x, ref residualy, n, nx, ny, aterm, ref v, _params) )
+            {
+                zerofill(s, nx, ny, bf, _params);
+                rep.terminationtype = -5;
+                progress10000 = 10000;
+                return;
+            }
+            
+            //
+            // Handle special case: multilayer model with NLayers=0.
+            // Quick exit.
+            //
+            if( nh==0 )
+            {
+                rep.terminationtype = 1;
+                zerofill(s, nx, ny, bf, _params);
+                for(i=0; i<=ny-1; i++)
+                {
+                    for(j=0; j<=nx; j++)
+                    {
+                        s.v[i,j] = v[i,j];
+                    }
+                }
+                rep.maxerror = 0;
+                rep.rmserror = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        rep.maxerror = Math.Max(rep.maxerror, Math.Abs(residualy[i,j]));
+                        rep.rmserror = rep.rmserror+math.sqr(residualy[i,j]);
+                    }
+                }
+                rep.rmserror = Math.Sqrt(rep.rmserror/(n*ny));
+                progress10000 = 10000;
+                return;
+            }
+            
+            //
+            // Penalty coefficient is set to LambdaNS*RBase^2.
+            //
+            // We use such normalization because VALUES of radial basis
+            // functions have roughly unit magnitude, but their DERIVATIVES
+            // are (roughly) inversely proportional to the radius. Thus,
+            // without additional scaling, regularization coefficient
+            // looses invariancy w.r.t. scaling of variables.
+            //
+            if( (double)(lambdans)==(double)(0) )
+            {
+                rowsperpoint = 1;
+            }
+            else
+            {
+                
+                //
+                // NOTE: simplified penalty function is used, which does not provide rotation invariance
+                //
+                rowsperpoint = 1+nx;
+            }
+            penalty = lambdans*math.sqr(rbase);
+            
+            //
+            // Prepare temporary structures
+            //
+            rhs = new double[n*rowsperpoint, ny];
+            curxy = new double[n, nx+ny];
+            x0 = new double[nx];
+            x1 = new double[nx];
+            tags = new int[n];
+            dist = new double[n];
+            vr2 = new double[n];
+            voffs = new int[n];
+            nncnt = new int[n];
+            rowsizes = new int[n*rowsperpoint];
+            denseb1 = new double[n*rowsperpoint];
+            for(i=0; i<=n*rowsperpoint-1; i++)
+            {
+                for(j=0; j<=ny-1; j++)
+                {
+                    rhs[i,j] = 0;
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=nx-1; j++)
+                {
+                    curxy[i,j] = x[i,j]/scalevec[j];
+                }
+                for(j=0; j<=ny-1; j++)
+                {
+                    rhs[i*rowsperpoint,j] = residualy[i,j];
+                }
+                tags[i] = i;
+            }
+            nearestneighbor.kdtreebuildtagged(curxy, tags, n, nx, 0, 2, globaltree, _params);
+            
+            //
+            // Generate sequence of layer radii.
+            // Prepare assignment of different levels to points.
+            //
+            alglib.ap.assert(n>0, "RBFV2BuildHierarchical: integrity check failed");
+            ri = new double[nh];
+            for(levelidx=0; levelidx<=nh-1; levelidx++)
+            {
+                ri[levelidx] = rbase*Math.Pow(2, -levelidx);
+            }
+            hidx = new int[n];
+            xr = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                hidx[i] = nh;
+                xr[i] = math.maxrealnumber;
+                alglib.ap.assert((double)(xr[i])>(double)(ri[0]), "RBFV2BuildHierarchical: integrity check failed");
+            }
+            for(levelidx=0; levelidx<=nh-1; levelidx++)
+            {
+                
+                //
+                // Scan dataset points, for each such point that distance to nearest
+                // "support" point is larger than SupportR*Ri[LevelIdx] we:
+                // * set distance of current point to 0 (it is support now) and update HIdx
+                // * perform R-NN request with radius SupportR*Ri[LevelIdx]
+                // * for each point in request update its distance
+                //
+                criticalr = s.supportr*ri[levelidx];
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (double)(xr[i])>(double)(criticalr) )
+                    {
+                        
+                        //
+                        // Mark point as support
+                        //
+                        alglib.ap.assert(hidx[i]==nh, "RBFV2BuildHierarchical: integrity check failed");
+                        hidx[i] = levelidx;
+                        xr[i] = 0;
+                        
+                        //
+                        // Update neighbors
+                        //
+                        for(j=0; j<=nx-1; j++)
+                        {
+                            x0[j] = x[i,j]/scalevec[j];
+                        }
+                        k = nearestneighbor.kdtreequeryrnn(globaltree, x0, criticalr, true, _params);
+                        nearestneighbor.kdtreequeryresultstags(globaltree, ref tags, _params);
+                        nearestneighbor.kdtreequeryresultsdistances(globaltree, ref dist, _params);
+                        for(j=0; j<=k-1; j++)
+                        {
+                            xr[tags[j]] = Math.Min(xr[tags[j]], dist[j]);
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Build multitree (with zero weights) according to hierarchy.
+            //
+            // NOTE: this code assumes that during every iteration kdNodes,
+            //       kdSplits and CW have size which EXACTLY fits their
+            //       contents, and that these variables are resized at each
+            //       iteration when we add new hierarchical model.
+            //
+            kdroots = new int[nh+1];
+            kdnodes = new int[0];
+            kdsplits = new double[0];
+            kdboxmin = new double[nx];
+            kdboxmax = new double[nx];
+            cw = new double[0];
+            cwrange = new int[nh+1];
+            nearestneighbor.kdtreeexplorebox(globaltree, ref kdboxmin, ref kdboxmax, _params);
+            cwrange[0] = 0;
+            for(levelidx=0; levelidx<=nh-1; levelidx++)
+            {
+                
+                //
+                // Prepare radius and root offset
+                //
+                kdroots[levelidx] = alglib.ap.len(kdnodes);
+                
+                //
+                // Generate LevelIdx-th tree and append to multi-tree
+                //
+                curn = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( hidx[i]<=levelidx )
+                    {
+                        for(j=0; j<=nx-1; j++)
+                        {
+                            curxy[curn,j] = x[i,j]/scalevec[j];
+                        }
+                        for(j=0; j<=ny-1; j++)
+                        {
+                            curxy[curn,nx+j] = 0;
+                        }
+                        apserv.inc(ref curn, _params);
+                    }
+                }
+                alglib.ap.assert(curn>0, "RBFV2BuildHierarchical: integrity check failed");
+                nearestneighbor.kdtreebuild(curxy, curn, nx, ny, 2, curtree, _params);
+                convertandappendtree(curtree, curn, nx, ny, ref kdnodes, ref kdsplits, ref cw, _params);
+                
+                //
+                // Fill entry of CWRange (we assume that length of CW exactly fits its actual size)
+                //
+                cwrange[levelidx+1] = alglib.ap.len(cw);
+            }
+            kdroots[nh] = alglib.ap.len(kdnodes);
+            
+            //
+            // Prepare buffer and scaled dataset
+            //
+            allocatecalcbuffer(s, calcbuf, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=nx-1; j++)
+                {
+                    curxy[i,j] = x[i,j]/scalevec[j];
+                }
+            }
+            
+            //
+            // Calculate average row sizes for each layer; these values are used
+            // for smooth progress reporting (it adds some overhead, but in most
+            // cases - insignificant one).
+            //
+            apserv.rvectorsetlengthatleast(ref avgrowsize, nh, _params);
+            sumrowsize = 0;
+            for(levelidx=0; levelidx<=nh-1; levelidx++)
+            {
+                cnt = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        x0[j] = curxy[i,j];
+                    }
+                    cnt = cnt+designmatrixrowsize(kdnodes, kdsplits, cw, ri, kdroots, kdboxmin, kdboxmax, nx, ny, nh, levelidx, rbfv2nearradius(bf, _params), x0, calcbuf, _params);
+                }
+                avgrowsize[levelidx] = apserv.coalesce(cnt, 1, _params)/apserv.coalesce(n, 1, _params);
+                sumrowsize = sumrowsize+avgrowsize[levelidx];
+            }
+            
+            //
+            // Build unconstrained model with LSQR solver, applied layer by layer
+            //
+            for(levelidx=0; levelidx<=nh-1; levelidx++)
+            {
+                
+                //
+                // Generate A - matrix of basis functions (near radius is used)
+                //
+                // NOTE: AvgDiagATA is average value of diagonal element of A^T*A.
+                //       It is used to calculate value of Tikhonov regularization
+                //       coefficient.
+                //
+                nbasis = (cwrange[levelidx+1]-cwrange[levelidx])/(nx+ny);
+                alglib.ap.assert(cwrange[levelidx+1]-cwrange[levelidx]==nbasis*(nx+ny));
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        x0[j] = curxy[i,j];
+                    }
+                    cnt = designmatrixrowsize(kdnodes, kdsplits, cw, ri, kdroots, kdboxmin, kdboxmax, nx, ny, nh, levelidx, rbfv2nearradius(bf, _params), x0, calcbuf, _params);
+                    nncnt[i] = cnt;
+                    for(j=0; j<=rowsperpoint-1; j++)
+                    {
+                        rowsizes[i*rowsperpoint+j] = cnt;
+                    }
+                }
+                apserv.ivectorsetlengthatleast(ref rowindexes, nbasis, _params);
+                apserv.rvectorsetlengthatleast(ref rowvals, nbasis*rowsperpoint, _params);
+                apserv.rvectorsetlengthatleast(ref diagata, nbasis, _params);
+                sparse.sparsecreatecrsbuf(n*rowsperpoint, nbasis, rowsizes, sparseacrs, _params);
+                avgdiagata = 0.0;
+                for(j=0; j<=nbasis-1; j++)
+                {
+                    diagata[j] = 0;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Fill design matrix row, diagonal of A^T*A
+                    //
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        x0[j] = curxy[i,j];
+                    }
+                    designmatrixgeneraterow(kdnodes, kdsplits, cw, ri, kdroots, kdboxmin, kdboxmax, cwrange, nx, ny, nh, levelidx, bf, rbfv2nearradius(bf, _params), rowsperpoint, penalty, x0, calcbuf, vr2, voffs, rowindexes, rowvals, ref cnt, _params);
+                    alglib.ap.assert(cnt==nncnt[i], "RBFV2BuildHierarchical: integrity check failed");
+                    for(k=0; k<=rowsperpoint-1; k++)
+                    {
+                        for(j=0; j<=cnt-1; j++)
+                        {
+                            val = rowvals[j*rowsperpoint+k];
+                            sparse.sparseset(sparseacrs, i*rowsperpoint+k, rowindexes[j], val, _params);
+                            avgdiagata = avgdiagata+math.sqr(val);
+                            diagata[rowindexes[j]] = diagata[rowindexes[j]]+math.sqr(val);
+                        }
+                    }
+                    
+                    //
+                    // Handle possible termination requests
+                    //
+                    if( terminationrequest )
+                    {
+                        
+                        //
+                        // Request for termination was submitted, terminate immediately
+                        //
+                        zerofill(s, nx, ny, bf, _params);
+                        rep.terminationtype = 8;
+                        progress10000 = 10000;
+                        return;
+                    }
+                }
+                avgdiagata = avgdiagata/nbasis;
+                apserv.rvectorsetlengthatleast(ref prec, nbasis, _params);
+                for(j=0; j<=nbasis-1; j++)
+                {
+                    prec[j] = 1/apserv.coalesce(Math.Sqrt(diagata[j]), 1, _params);
+                }
+                
+                //
+                // solve
+                //
+                maxits = apserv.coalescei(s.maxits, defaultmaxits, _params);
+                apserv.rvectorsetlengthatleast(ref tmpx, nbasis, _params);
+                linlsqr.linlsqrcreate(n*rowsperpoint, nbasis, linstate, _params);
+                linlsqr.linlsqrsetcond(linstate, 0.0, 0.0, maxits, _params);
+                linlsqr.linlsqrsetlambdai(linstate, Math.Sqrt(s.lambdareg*avgdiagata), _params);
+                for(j=0; j<=ny-1; j++)
+                {
+                    for(i=0; i<=n*rowsperpoint-1; i++)
+                    {
+                        denseb1[i] = rhs[i,j];
+                    }
+                    linlsqr.linlsqrsetb(linstate, denseb1, _params);
+                    linlsqr.linlsqrrestart(linstate, _params);
+                    linlsqr.linlsqrsetxrep(linstate, true, _params);
+                    while( linlsqr.linlsqriteration(linstate, _params) )
+                    {
+                        if( terminationrequest )
+                        {
+                            
+                            //
+                            // Request for termination was submitted, terminate immediately
+                            //
+                            zerofill(s, nx, ny, bf, _params);
+                            rep.terminationtype = 8;
+                            progress10000 = 10000;
+                            return;
+                        }
+                        if( linstate.needmv )
+                        {
+                            for(i=0; i<=nbasis-1; i++)
+                            {
+                                tmpx[i] = prec[i]*linstate.x[i];
+                            }
+                            sparse.sparsemv(sparseacrs, tmpx, ref linstate.mv, _params);
+                            continue;
+                        }
+                        if( linstate.needmtv )
+                        {
+                            sparse.sparsemtv(sparseacrs, linstate.x, ref linstate.mtv, _params);
+                            for(i=0; i<=nbasis-1; i++)
+                            {
+                                linstate.mtv[i] = prec[i]*linstate.mtv[i];
+                            }
+                            continue;
+                        }
+                        if( linstate.xupdated )
+                        {
+                            rprogress = 0;
+                            for(i=0; i<=levelidx-1; i++)
+                            {
+                                rprogress = rprogress+maxits*ny*avgrowsize[i];
+                            }
+                            rprogress = rprogress+(linlsqr.linlsqrpeekiterationscount(linstate, _params)+j*maxits)*avgrowsize[levelidx];
+                            rprogress = rprogress/(sumrowsize*maxits*ny);
+                            rprogress = 10000*rprogress;
+                            rprogress = Math.Max(rprogress, 0);
+                            rprogress = Math.Min(rprogress, 10000);
+                            alglib.ap.assert(progress10000<=(int)Math.Round(rprogress)+1, "HRBF: integrity check failed (progress indicator) even after +1 safeguard correction");
+                            progress10000 = (int)Math.Round(rprogress);
+                            continue;
+                        }
+                        alglib.ap.assert(false, "HRBF: unexpected request from LSQR solver");
+                    }
+                    linlsqr.linlsqrresults(linstate, ref densew1, lsqrrep, _params);
+                    alglib.ap.assert(lsqrrep.terminationtype>0, "RBFV2BuildHierarchical: integrity check failed");
+                    for(i=0; i<=nbasis-1; i++)
+                    {
+                        densew1[i] = prec[i]*densew1[i];
+                    }
+                    for(i=0; i<=nbasis-1; i++)
+                    {
+                        offsi = cwrange[levelidx]+(nx+ny)*i;
+                        cw[offsi+nx+j] = densew1[i];
+                    }
+                }
+                
+                //
+                // Update residuals (far radius is used)
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        x0[j] = curxy[i,j];
+                    }
+                    designmatrixgeneraterow(kdnodes, kdsplits, cw, ri, kdroots, kdboxmin, kdboxmax, cwrange, nx, ny, nh, levelidx, bf, rbfv2farradius(bf, _params), rowsperpoint, penalty, x0, calcbuf, vr2, voffs, rowindexes, rowvals, ref cnt, _params);
+                    for(j=0; j<=cnt-1; j++)
+                    {
+                        offsj = cwrange[levelidx]+(nx+ny)*rowindexes[j]+nx;
+                        for(k=0; k<=rowsperpoint-1; k++)
+                        {
+                            val = rowvals[j*rowsperpoint+k];
+                            for(k2=0; k2<=ny-1; k2++)
+                            {
+                                rhs[i*rowsperpoint+k,k2] = rhs[i*rowsperpoint+k,k2]-val*cw[offsj+k2];
+                            }
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Model is built.
+            //
+            // Copy local variables by swapping, global ones (ScaleVec) are copied
+            // explicitly.
+            //
+            s.bf = bf;
+            s.nh = nh;
+            alglib.ap.swap(ref s.ri, ref ri);
+            alglib.ap.swap(ref s.kdroots, ref kdroots);
+            alglib.ap.swap(ref s.kdnodes, ref kdnodes);
+            alglib.ap.swap(ref s.kdsplits, ref kdsplits);
+            alglib.ap.swap(ref s.kdboxmin, ref kdboxmin);
+            alglib.ap.swap(ref s.kdboxmax, ref kdboxmax);
+            alglib.ap.swap(ref s.cw, ref cw);
+            alglib.ap.swap(ref s.v, ref v);
+            s.s = new double[nx];
+            for(i=0; i<=nx-1; i++)
+            {
+                s.s[i] = scalevec[i];
+            }
+            rep.terminationtype = 1;
+            
+            //
+            // Calculate maximum and RMS errors
+            //
+            rep.maxerror = 0;
+            rep.rmserror = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=ny-1; j++)
+                {
+                    rep.maxerror = Math.Max(rep.maxerror, Math.Abs(rhs[i*rowsperpoint,j]));
+                    rep.rmserror = rep.rmserror+math.sqr(rhs[i*rowsperpoint,j]);
+                }
+            }
+            rep.rmserror = Math.Sqrt(rep.rmserror/(n*ny));
+            
+            //
+            // Update progress reports
+            //
+            progress10000 = 10000;
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 02.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfv2alloc(alglib.serializer s,
+            rbfv2model model,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Data
+            //
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            apserv.allocrealarray(s, model.ri, -1, _params);
+            apserv.allocrealarray(s, model.s, -1, _params);
+            apserv.allocintegerarray(s, model.kdroots, -1, _params);
+            apserv.allocintegerarray(s, model.kdnodes, -1, _params);
+            apserv.allocrealarray(s, model.kdsplits, -1, _params);
+            apserv.allocrealarray(s, model.kdboxmin, -1, _params);
+            apserv.allocrealarray(s, model.kdboxmax, -1, _params);
+            apserv.allocrealarray(s, model.cw, -1, _params);
+            apserv.allocrealmatrix(s, model.v, -1, -1, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 02.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfv2serialize(alglib.serializer s,
+            rbfv2model model,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Data
+            //
+            s.serialize_int(model.nx);
+            s.serialize_int(model.ny);
+            s.serialize_int(model.nh);
+            s.serialize_int(model.bf);
+            apserv.serializerealarray(s, model.ri, -1, _params);
+            apserv.serializerealarray(s, model.s, -1, _params);
+            apserv.serializeintegerarray(s, model.kdroots, -1, _params);
+            apserv.serializeintegerarray(s, model.kdnodes, -1, _params);
+            apserv.serializerealarray(s, model.kdsplits, -1, _params);
+            apserv.serializerealarray(s, model.kdboxmin, -1, _params);
+            apserv.serializerealarray(s, model.kdboxmax, -1, _params);
+            apserv.serializerealarray(s, model.cw, -1, _params);
+            apserv.serializerealmatrix(s, model.v, -1, -1, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 02.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfv2unserialize(alglib.serializer s,
+            rbfv2model model,
+            alglib.xparams _params)
+        {
+            int nx = 0;
+            int ny = 0;
+
+            
+            //
+            // 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();
+            rbfv2create(nx, ny, model, _params);
+            model.nh = s.unserialize_int();
+            model.bf = s.unserialize_int();
+            apserv.unserializerealarray(s, ref model.ri, _params);
+            apserv.unserializerealarray(s, ref model.s, _params);
+            apserv.unserializeintegerarray(s, ref model.kdroots, _params);
+            apserv.unserializeintegerarray(s, ref model.kdnodes, _params);
+            apserv.unserializerealarray(s, ref model.kdsplits, _params);
+            apserv.unserializerealarray(s, ref model.kdboxmin, _params);
+            apserv.unserializerealarray(s, ref model.kdboxmax, _params);
+            apserv.unserializerealarray(s, ref model.cw, _params);
+            apserv.unserializerealmatrix(s, ref model.v, _params);
+        }
+
+
+        /*************************************************************************
+        Returns far radius for basis function type
+        *************************************************************************/
+        public static double rbfv2farradius(int bf,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = 1;
+            if( bf==0 )
+            {
+                result = 5.0;
+            }
+            if( bf==1 )
+            {
+                result = 3;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns near radius for basis function type
+        *************************************************************************/
+        public static double rbfv2nearradius(int bf,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = 1;
+            if( bf==0 )
+            {
+                result = 3.0;
+            }
+            if( bf==1 )
+            {
+                result = 3;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns basis function value.
+        Assumes that D2>=0
+        *************************************************************************/
+        public static double rbfv2basisfunc(int bf,
+            double d2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double v = 0;
+
+            result = 0;
+            if( bf==0 )
+            {
+                result = Math.Exp(-d2);
+                return result;
+            }
+            if( bf==1 )
+            {
+                
+                //
+                // if D2<3:
+                //     Exp(1)*Exp(-D2)*Exp(-1/(1-D2/9))
+                // else:
+                //     0
+                //
+                v = 1-d2/9;
+                if( (double)(v)<=(double)(0) )
+                {
+                    result = 0;
+                    return result;
+                }
+                result = 2.718281828459045*Math.Exp(-d2)*Math.Exp(-(1/v));
+                return result;
+            }
+            alglib.ap.assert(false, "RBFV2BasisFunc: unknown BF type");
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns basis function value, first and second derivatives
+        Assumes that D2>=0
+        *************************************************************************/
+        public static void rbfv2basisfuncdiff2(int bf,
+            double d2,
+            ref double f,
+            ref double df,
+            ref double d2f,
+            alglib.xparams _params)
+        {
+            double v = 0;
+
+            f = 0;
+            df = 0;
+            d2f = 0;
+
+            if( bf==0 )
+            {
+                f = Math.Exp(-d2);
+                df = -f;
+                d2f = f;
+                return;
+            }
+            if( bf==1 )
+            {
+                
+                //
+                // if D2<3:
+                //       F = Exp(1)*Exp(-D2)*Exp(-1/(1-D2/9))
+                //      dF =  -F * [pow(D2/9-1,-2)/9 + 1]
+                //     d2F = -dF * [pow(D2/9-1,-2)/9 + 1] + F*(2/81)*pow(D2/9-1,-3)
+                // else:
+                //     0
+                //
+                v = 1-d2/9;
+                if( (double)(v)<=(double)(0) )
+                {
+                    f = 0;
+                    df = 0;
+                    d2f = 0;
+                    return;
+                }
+                f = Math.Exp(1)*Math.Exp(-d2)*Math.Exp(-(1/v));
+                df = -(f*(1/(9*v*v)+1));
+                d2f = -(df*(1/(9*v*v)+1))+f*((double)2/(double)81)/(v*v*v);
+                return;
+            }
+            alglib.ap.assert(false, "RBFV2BasisFuncDiff2: unknown BF type");
+        }
+
+
+        /*************************************************************************
+        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=1
+        (1-dimensional space).
+
+        This function returns 0.0 when:
+        * model is not initialized
+        * NX<>1
+         *NY<>1
+
+        INPUT PARAMETERS:
+            S       -   RBF model
+            X0      -   X-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 rbfv2calc1(rbfv2model s,
+            double x0,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(math.isfinite(x0), "RBFCalc1: invalid value for X0 (X0 is Inf)!");
+            if( s.ny!=1 || s.nx!=1 )
+            {
+                result = 0;
+                return result;
+            }
+            result = s.v[0,0]*x0-s.v[0,1];
+            if( s.nh==0 )
+            {
+                return result;
+            }
+            allocatecalcbuffer(s, s.calcbuf, _params);
+            s.calcbuf.x123[0] = x0;
+            rbfv2tscalcbuf(s, s.calcbuf, s.calcbuf.x123, ref s.calcbuf.y123, _params);
+            result = s.calcbuf.y123[0];
+            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=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 rbfv2calc2(rbfv2model s,
+            double x0,
+            double x1,
+            alglib.xparams _params)
+        {
+            double result = 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)!");
+            if( s.ny!=1 || s.nx!=2 )
+            {
+                result = 0;
+                return result;
+            }
+            result = s.v[0,0]*x0+s.v[0,1]*x1+s.v[0,2];
+            if( s.nh==0 )
+            {
+                return result;
+            }
+            allocatecalcbuffer(s, s.calcbuf, _params);
+            s.calcbuf.x123[0] = x0;
+            s.calcbuf.x123[1] = x1;
+            rbfv2tscalcbuf(s, s.calcbuf, s.calcbuf.x123, ref s.calcbuf.y123, _params);
+            result = s.calcbuf.y123[0];
+            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 rbfv2calc3(rbfv2model s,
+            double x0,
+            double x1,
+            double x2,
+            alglib.xparams _params)
+        {
+            double result = 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)!");
+            if( s.ny!=1 || s.nx!=3 )
+            {
+                result = 0;
+                return result;
+            }
+            result = s.v[0,0]*x0+s.v[0,1]*x1+s.v[0,2]*x2+s.v[0,3];
+            if( s.nh==0 )
+            {
+                return result;
+            }
+            allocatecalcbuffer(s, s.calcbuf, _params);
+            s.calcbuf.x123[0] = x0;
+            s.calcbuf.x123[1] = x1;
+            s.calcbuf.x123[2] = x2;
+            rbfv2tscalcbuf(s, s.calcbuf, s.calcbuf.x123, ref s.calcbuf.y123, _params);
+            result = s.calcbuf.y123[0];
+            return result;
+        }
+
+
+        /*************************************************************************
+        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 rbfv2calcbuf(rbfv2model s,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            rbfv2tscalcbuf(s, s.calcbuf, x, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF model at the given point, using
+        external  buffer  object  (internal  temporaries  of  RBF  model  are  not
+        modified).
+
+        This function allows to use same RBF model object  in  different  threads,
+        assuming  that  different   threads  use  different  instances  of  buffer
+        structure.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, may be shared between different threads
+            Buf     -   buffer object created for this particular instance of  RBF
+                        model with rbfcreatecalcbuffer().
+            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 rbfv2tscalcbuf(rbfv2model s,
+            rbfv2calcbuffer buf,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int levelidx = 0;
+            double rcur = 0;
+            double rquery2 = 0;
+            double invrc2 = 0;
+            int nx = 0;
+            int ny = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=s.nx, "RBFCalcBuf: Length(X)<NX");
+            alglib.ap.assert(apserv.isfinitevector(x, s.nx, _params), "RBFCalcBuf: X contains infinite or NaN values");
+            nx = s.nx;
+            ny = s.ny;
+            
+            //
+            // Handle linear term
+            //
+            if( alglib.ap.len(y)<ny )
+            {
+                y = new double[ny];
+            }
+            for(i=0; i<=ny-1; i++)
+            {
+                y[i] = s.v[i,nx];
+                for(j=0; j<=nx-1; j++)
+                {
+                    y[i] = y[i]+s.v[i,j]*x[j];
+                }
+            }
+            if( s.nh==0 )
+            {
+                return;
+            }
+            
+            //
+            // Handle nonlinear term
+            //
+            allocatecalcbuffer(s, buf, _params);
+            for(j=0; j<=nx-1; j++)
+            {
+                buf.x[j] = x[j]/s.s[j];
+            }
+            for(levelidx=0; levelidx<=s.nh-1; levelidx++)
+            {
+                
+                //
+                // Prepare fields of Buf required by PartialCalcRec()
+                //
+                buf.curdist2 = 0;
+                for(j=0; j<=nx-1; j++)
+                {
+                    buf.curboxmin[j] = s.kdboxmin[j];
+                    buf.curboxmax[j] = s.kdboxmax[j];
+                    if( (double)(buf.x[j])<(double)(buf.curboxmin[j]) )
+                    {
+                        buf.curdist2 = buf.curdist2+math.sqr(buf.curboxmin[j]-buf.x[j]);
+                    }
+                    else
+                    {
+                        if( (double)(buf.x[j])>(double)(buf.curboxmax[j]) )
+                        {
+                            buf.curdist2 = buf.curdist2+math.sqr(buf.x[j]-buf.curboxmax[j]);
+                        }
+                    }
+                }
+                
+                //
+                // Call PartialCalcRec()
+                //
+                rcur = s.ri[levelidx];
+                invrc2 = 1/(rcur*rcur);
+                rquery2 = math.sqr(rcur*rbfv2farradius(s.bf, _params));
+                partialcalcrec(s, buf, s.kdroots[levelidx], invrc2, rquery2, buf.x, y, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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.
+                        
+        NOTE: as a special exception, this function supports unordered  arrays  X0
+              and X1. However, future versions may be  more  efficient  for  X0/X1
+              ordered by ascending.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfv2gridcalc2(rbfv2model s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            ref double[,] y,
+            alglib.xparams _params)
+        {
+            double[] cpx0 = new double[0];
+            double[] cpx1 = new double[0];
+            double[] dummyx2 = new double[0];
+            double[] dummyx3 = new double[0];
+            bool[] dummyflag = new bool[0];
+            int[] p01 = new int[0];
+            int[] p11 = new int[0];
+            int[] p2 = new int[0];
+            double[] vy = new double[0];
+            int i = 0;
+            int j = 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, _params), "RBFGridCalc2: X0 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x1, n1, _params), "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 )
+            {
+                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, _params);
+            cpx1 = new double[n1];
+            for(i=0; i<=n1-1; i++)
+            {
+                cpx1[i] = x1[i];
+            }
+            tsort.tagsort(ref cpx1, n1, ref p11, ref p2, _params);
+            dummyx2 = new double[1];
+            dummyx2[0] = 0;
+            dummyx3 = new double[1];
+            dummyx3[0] = 0;
+            vy = new double[n0*n1];
+            rbfv2gridcalcvx(s, cpx0, n0, cpx1, n1, dummyx2, 1, dummyx3, 1, dummyflag, false, vy, _params);
+            for(i=0; i<=n0-1; i++)
+            {
+                for(j=0; j<=n1-1; j++)
+                {
+                    y[i,j] = vy[i+j*n0];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function is used to perform gridded calculation  for  2D,  3D  or  4D
+        problems. It accepts parameters X0...X3 and counters N0...N3. If RBF model
+        has dimensionality less than 4, corresponding arrays should  contain  just
+        one element equal to zero, and corresponding N's should be equal to 1.
+
+        NOTE: array Y should be preallocated by caller.
+
+          -- ALGLIB --
+             Copyright 12.07.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfv2gridcalcvx(rbfv2model s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            double[] x2,
+            int n2,
+            double[] x3,
+            int n3,
+            bool[] flagy,
+            bool sparsey,
+            double[] y,
+            alglib.xparams _params)
+        {
+            int nx = 0;
+            int ny = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double[] tx = new double[0];
+            double[] ty = new double[0];
+            double[] z = new double[0];
+            int dstoffs = 0;
+            int dummy = 0;
+            rbfv2gridcalcbuffer bufseedv2 = new rbfv2gridcalcbuffer();
+            alglib.smp.shared_pool bufpool = new alglib.smp.shared_pool();
+            int rowidx = 0;
+            int rowcnt = 0;
+            double v = 0;
+            double rcur = 0;
+            int levelidx = 0;
+            double searchradius2 = 0;
+            int ntrials = 0;
+            double avgfuncpernode = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+            int[] blocks0 = new int[0];
+            int[] blocks1 = new int[0];
+            int[] blocks2 = new int[0];
+            int[] blocks3 = new int[0];
+            int blockscnt0 = 0;
+            int blockscnt1 = 0;
+            int blockscnt2 = 0;
+            int blockscnt3 = 0;
+            double blockwidth0 = 0;
+            double blockwidth1 = 0;
+            double blockwidth2 = 0;
+            double blockwidth3 = 0;
+            int maxblocksize = 0;
+
+            nx = s.nx;
+            ny = s.ny;
+            hqrnd.hqrndseed(532, 54734, rs, _params);
+            
+            //
+            // Perform integrity checks
+            //
+            alglib.ap.assert(s.nx==2 || s.nx==3, "RBFGridCalcVX: integrity check failed");
+            alglib.ap.assert(s.nx>=4 || ((alglib.ap.len(x3)>=1 && (double)(x3[0])==(double)(0)) && n3==1), "RBFGridCalcVX: integrity check failed");
+            alglib.ap.assert(s.nx>=3 || ((alglib.ap.len(x2)>=1 && (double)(x2[0])==(double)(0)) && n2==1), "RBFGridCalcVX: integrity check failed");
+            alglib.ap.assert(s.nx>=2 || ((alglib.ap.len(x1)>=1 && (double)(x1[0])==(double)(0)) && n1==1), "RBFGridCalcVX: integrity check failed");
+            
+            //
+            // Allocate arrays
+            //
+            alglib.ap.assert(s.nx<=4, "RBFGridCalcVX: integrity check failed");
+            z = new double[ny];
+            tx = new double[4];
+            ty = new double[ny];
+            
+            //
+            // Calculate linear term
+            //
+            rowcnt = n1*n2*n3;
+            for(rowidx=0; rowidx<=rowcnt-1; rowidx++)
+            {
+                
+                //
+                // Calculate TX - current position
+                //
+                k = rowidx;
+                tx[0] = 0;
+                tx[1] = x1[k%n1];
+                k = k/n1;
+                tx[2] = x2[k%n2];
+                k = k/n2;
+                tx[3] = x3[k%n3];
+                k = k/n3;
+                alglib.ap.assert(k==0, "RBFGridCalcVX: integrity check failed");
+                for(j=0; j<=ny-1; j++)
+                {
+                    v = s.v[j,nx];
+                    for(k=1; k<=nx-1; k++)
+                    {
+                        v = v+tx[k]*s.v[j,k];
+                    }
+                    z[j] = v;
+                }
+                for(i=0; i<=n0-1; i++)
+                {
+                    dstoffs = ny*(rowidx*n0+i);
+                    if( sparsey && !flagy[rowidx*n0+i] )
+                    {
+                        for(j=0; j<=ny-1; j++)
+                        {
+                            y[j+dstoffs] = 0;
+                        }
+                        continue;
+                    }
+                    v = x0[i];
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        y[j+dstoffs] = z[j]+v*s.v[j,0];
+                    }
+                }
+            }
+            if( s.nh==0 )
+            {
+                return;
+            }
+            
+            //
+            // Process RBF terms, layer by layer
+            //
+            for(levelidx=0; levelidx<=s.nh-1; levelidx++)
+            {
+                rcur = s.ri[levelidx];
+                blockwidth0 = 1;
+                blockwidth1 = 1;
+                blockwidth2 = 1;
+                blockwidth3 = 1;
+                if( nx>=1 )
+                {
+                    blockwidth0 = rcur*s.s[0];
+                }
+                if( nx>=2 )
+                {
+                    blockwidth1 = rcur*s.s[1];
+                }
+                if( nx>=3 )
+                {
+                    blockwidth2 = rcur*s.s[2];
+                }
+                if( nx>=4 )
+                {
+                    blockwidth3 = rcur*s.s[3];
+                }
+                maxblocksize = 8;
+                
+                //
+                // Group grid nodes into blocks according to current radius
+                //
+                blocks0 = new int[n0+1];
+                blockscnt0 = 0;
+                blocks0[0] = 0;
+                for(i=1; i<=n0-1; i++)
+                {
+                    if( (double)(x0[i]-x0[blocks0[blockscnt0]])>(double)(blockwidth0) || i-blocks0[blockscnt0]>=maxblocksize )
+                    {
+                        apserv.inc(ref blockscnt0, _params);
+                        blocks0[blockscnt0] = i;
+                    }
+                }
+                apserv.inc(ref blockscnt0, _params);
+                blocks0[blockscnt0] = n0;
+                blocks1 = new int[n1+1];
+                blockscnt1 = 0;
+                blocks1[0] = 0;
+                for(i=1; i<=n1-1; i++)
+                {
+                    if( (double)(x1[i]-x1[blocks1[blockscnt1]])>(double)(blockwidth1) || i-blocks1[blockscnt1]>=maxblocksize )
+                    {
+                        apserv.inc(ref blockscnt1, _params);
+                        blocks1[blockscnt1] = i;
+                    }
+                }
+                apserv.inc(ref blockscnt1, _params);
+                blocks1[blockscnt1] = n1;
+                blocks2 = new int[n2+1];
+                blockscnt2 = 0;
+                blocks2[0] = 0;
+                for(i=1; i<=n2-1; i++)
+                {
+                    if( (double)(x2[i]-x2[blocks2[blockscnt2]])>(double)(blockwidth2) || i-blocks2[blockscnt2]>=maxblocksize )
+                    {
+                        apserv.inc(ref blockscnt2, _params);
+                        blocks2[blockscnt2] = i;
+                    }
+                }
+                apserv.inc(ref blockscnt2, _params);
+                blocks2[blockscnt2] = n2;
+                blocks3 = new int[n3+1];
+                blockscnt3 = 0;
+                blocks3[0] = 0;
+                for(i=1; i<=n3-1; i++)
+                {
+                    if( (double)(x3[i]-x3[blocks3[blockscnt3]])>(double)(blockwidth3) || i-blocks3[blockscnt3]>=maxblocksize )
+                    {
+                        apserv.inc(ref blockscnt3, _params);
+                        blocks3[blockscnt3] = i;
+                    }
+                }
+                apserv.inc(ref blockscnt3, _params);
+                blocks3[blockscnt3] = n3;
+                
+                //
+                // Prepare seed for shared pool
+                //
+                allocatecalcbuffer(s, bufseedv2.calcbuf, _params);
+                alglib.smp.ae_shared_pool_set_seed(bufpool, bufseedv2);
+                
+                //
+                // Determine average number of neighbor per node
+                //
+                searchradius2 = math.sqr(rcur*rbfv2farradius(s.bf, _params));
+                ntrials = 100;
+                avgfuncpernode = 0.0;
+                for(i=0; i<=ntrials-1; i++)
+                {
+                    tx[0] = x0[hqrnd.hqrnduniformi(rs, n0, _params)];
+                    tx[1] = x1[hqrnd.hqrnduniformi(rs, n1, _params)];
+                    tx[2] = x2[hqrnd.hqrnduniformi(rs, n2, _params)];
+                    tx[3] = x3[hqrnd.hqrnduniformi(rs, n3, _params)];
+                    preparepartialquery(tx, s.kdboxmin, s.kdboxmax, nx, bufseedv2.calcbuf, ref dummy, _params);
+                    avgfuncpernode = avgfuncpernode+(double)partialcountrec(s.kdnodes, s.kdsplits, s.cw, nx, ny, bufseedv2.calcbuf, s.kdroots[levelidx], searchradius2, tx, _params)/(double)ntrials;
+                }
+                
+                //
+                // Perform calculation in multithreaded mode
+                //
+                rbfv2partialgridcalcrec(s, x0, n0, x1, n1, x2, n2, x3, n3, blocks0, 0, blockscnt0, blocks1, 0, blockscnt1, blocks2, 0, blockscnt2, blocks3, 0, blockscnt3, flagy, sparsey, levelidx, avgfuncpernode, bufpool, y, _params);
+            }
+        }
+
+
+        public static void rbfv2partialgridcalcrec(rbfv2model s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            double[] x2,
+            int n2,
+            double[] x3,
+            int n3,
+            int[] blocks0,
+            int block0a,
+            int block0b,
+            int[] blocks1,
+            int block1a,
+            int block1b,
+            int[] blocks2,
+            int block2a,
+            int block2b,
+            int[] blocks3,
+            int block3a,
+            int block3b,
+            bool[] flagy,
+            bool sparsey,
+            int levelidx,
+            double avgfuncpernode,
+            alglib.smp.shared_pool bufpool,
+            double[] y,
+            alglib.xparams _params)
+        {
+            int nx = 0;
+            int ny = 0;
+            int k = 0;
+            int l = 0;
+            int blkidx = 0;
+            int blkcnt = 0;
+            int nodeidx = 0;
+            int nodescnt = 0;
+            int rowidx = 0;
+            int rowscnt = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int i3 = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int j2 = 0;
+            int j3 = 0;
+            double rcur = 0;
+            double invrc2 = 0;
+            double rquery2 = 0;
+            double rfar2 = 0;
+            int dstoffs = 0;
+            int srcoffs = 0;
+            int dummy = 0;
+            double rowwidth = 0;
+            double maxrowwidth = 0;
+            double problemcost = 0;
+            int maxbs = 0;
+            int midpoint = 0;
+            bool emptyrow = new bool();
+            rbfv2gridcalcbuffer buf = null;
+
+            nx = s.nx;
+            ny = s.ny;
+            
+            //
+            // Integrity checks
+            //
+            alglib.ap.assert(s.nx==2 || s.nx==3, "RBFV2PartialGridCalcRec: integrity check failed");
+            
+            //
+            // Try to split large problem
+            //
+            problemcost = s.ny*2*(avgfuncpernode+1);
+            problemcost = problemcost*(blocks0[block0b]-blocks0[block0a]);
+            problemcost = problemcost*(blocks1[block1b]-blocks1[block1a]);
+            problemcost = problemcost*(blocks2[block2b]-blocks2[block2a]);
+            problemcost = problemcost*(blocks3[block3b]-blocks3[block3a]);
+            maxbs = 0;
+            maxbs = Math.Max(maxbs, block0b-block0a);
+            maxbs = Math.Max(maxbs, block1b-block1a);
+            maxbs = Math.Max(maxbs, block2b-block2a);
+            maxbs = Math.Max(maxbs, block3b-block3a);
+            if( (double)(problemcost*complexitymultiplier)>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_rbfv2partialgridcalcrec(s,x0,n0,x1,n1,x2,n2,x3,n3,blocks0,block0a,block0b,blocks1,block1a,block1b,blocks2,block2a,block2b,blocks3,block3a,block3b,flagy,sparsey,levelidx,avgfuncpernode,bufpool,y, _params) )
+                {
+                    return;
+                }
+            }
+            if( (double)(problemcost*complexitymultiplier)>=(double)(apserv.spawnlevel(_params)) && maxbs>=2 )
+            {
+                if( block0b-block0a==maxbs )
+                {
+                    midpoint = block0a+maxbs/2;
+                    rbfv2partialgridcalcrec(s, x0, n0, x1, n1, x2, n2, x3, n3, blocks0, block0a, midpoint, blocks1, block1a, block1b, blocks2, block2a, block2b, blocks3, block3a, block3b, flagy, sparsey, levelidx, avgfuncpernode, bufpool, y, _params);
+                    rbfv2partialgridcalcrec(s, x0, n0, x1, n1, x2, n2, x3, n3, blocks0, midpoint, block0b, blocks1, block1a, block1b, blocks2, block2a, block2b, blocks3, block3a, block3b, flagy, sparsey, levelidx, avgfuncpernode, bufpool, y, _params);
+                    return;
+                }
+                if( block1b-block1a==maxbs )
+                {
+                    midpoint = block1a+maxbs/2;
+                    rbfv2partialgridcalcrec(s, x0, n0, x1, n1, x2, n2, x3, n3, blocks0, block0a, block0b, blocks1, block1a, midpoint, blocks2, block2a, block2b, blocks3, block3a, block3b, flagy, sparsey, levelidx, avgfuncpernode, bufpool, y, _params);
+                    rbfv2partialgridcalcrec(s, x0, n0, x1, n1, x2, n2, x3, n3, blocks0, block0a, block0b, blocks1, midpoint, block1b, blocks2, block2a, block2b, blocks3, block3a, block3b, flagy, sparsey, levelidx, avgfuncpernode, bufpool, y, _params);
+                    return;
+                }
+                if( block2b-block2a==maxbs )
+                {
+                    midpoint = block2a+maxbs/2;
+                    rbfv2partialgridcalcrec(s, x0, n0, x1, n1, x2, n2, x3, n3, blocks0, block0a, block0b, blocks1, block1a, block1b, blocks2, block2a, midpoint, blocks3, block3a, block3b, flagy, sparsey, levelidx, avgfuncpernode, bufpool, y, _params);
+                    rbfv2partialgridcalcrec(s, x0, n0, x1, n1, x2, n2, x3, n3, blocks0, block0a, block0b, blocks1, block1a, block1b, blocks2, midpoint, block2b, blocks3, block3a, block3b, flagy, sparsey, levelidx, avgfuncpernode, bufpool, y, _params);
+                    return;
+                }
+                if( block3b-block3a==maxbs )
+                {
+                    midpoint = block3a+maxbs/2;
+                    rbfv2partialgridcalcrec(s, x0, n0, x1, n1, x2, n2, x3, n3, blocks0, block0a, block0b, blocks1, block1a, block1b, blocks2, block2a, block2b, blocks3, block3a, midpoint, flagy, sparsey, levelidx, avgfuncpernode, bufpool, y, _params);
+                    rbfv2partialgridcalcrec(s, x0, n0, x1, n1, x2, n2, x3, n3, blocks0, block0a, block0b, blocks1, block1a, block1b, blocks2, block2a, block2b, blocks3, midpoint, block3b, flagy, sparsey, levelidx, avgfuncpernode, bufpool, y, _params);
+                    return;
+                }
+                alglib.ap.assert(false, "RBFV2PartialGridCalcRec: integrity check failed");
+            }
+            
+            //
+            // Retrieve buffer object from pool (it will be returned later)
+            //
+            alglib.smp.ae_shared_pool_retrieve(bufpool, ref buf);
+            
+            //
+            // Calculate RBF model
+            //
+            alglib.ap.assert(nx<=4, "RBFV2PartialGridCalcRec: integrity check failed");
+            buf.tx = new double[4];
+            buf.cx = new double[4];
+            buf.ty = new double[ny];
+            rcur = s.ri[levelidx];
+            invrc2 = 1/(rcur*rcur);
+            blkcnt = (block3b-block3a)*(block2b-block2a)*(block1b-block1a)*(block0b-block0a);
+            for(blkidx=0; blkidx<=blkcnt-1; blkidx++)
+            {
+                
+                //
+                // Select block (I0,I1,I2,I3).
+                //
+                // NOTE: for problems with NX<4 corresponding I_? are zero.
+                //
+                k = blkidx;
+                i0 = block0a+k%(block0b-block0a);
+                k = k/(block0b-block0a);
+                i1 = block1a+k%(block1b-block1a);
+                k = k/(block1b-block1a);
+                i2 = block2a+k%(block2b-block2a);
+                k = k/(block2b-block2a);
+                i3 = block3a+k%(block3b-block3a);
+                k = k/(block3b-block3a);
+                alglib.ap.assert(k==0, "RBFV2PartialGridCalcRec: integrity check failed");
+                
+                //
+                // We partitioned grid into blocks and selected block with
+                // index (I0,I1,I2,I3). This block is a 4D cube (some dimensions
+                // may be zero) of nodes with indexes (J0,J1,J2,J3), which is
+                // further partitioned into a set of rows, each row corresponding
+                // to indexes J1...J3 being fixed.
+                //
+                // We process block row by row, and each row may be handled
+                // by either "generic" (nodes are processed separately) or
+                // batch algorithm (that's the reason to use rows, after all).
+                //
+                //
+                // Process nodes of the block
+                //
+                rowscnt = (blocks3[i3+1]-blocks3[i3])*(blocks2[i2+1]-blocks2[i2])*(blocks1[i1+1]-blocks1[i1]);
+                for(rowidx=0; rowidx<=rowscnt-1; rowidx++)
+                {
+                    
+                    //
+                    // Find out node indexes (*,J1,J2,J3).
+                    //
+                    // NOTE: for problems with NX<4 corresponding J_? are zero.
+                    //
+                    k = rowidx;
+                    j1 = blocks1[i1]+k%(blocks1[i1+1]-blocks1[i1]);
+                    k = k/(blocks1[i1+1]-blocks1[i1]);
+                    j2 = blocks2[i2]+k%(blocks2[i2+1]-blocks2[i2]);
+                    k = k/(blocks2[i2+1]-blocks2[i2]);
+                    j3 = blocks3[i3]+k%(blocks3[i3+1]-blocks3[i3]);
+                    k = k/(blocks3[i3+1]-blocks3[i3]);
+                    alglib.ap.assert(k==0, "RBFV2PartialGridCalcRec: integrity check failed");
+                    
+                    //
+                    // Analyze row, skip completely empty rows
+                    //
+                    nodescnt = blocks0[i0+1]-blocks0[i0];
+                    srcoffs = blocks0[i0]+(j1+(j2+j3*n2)*n1)*n0;
+                    emptyrow = true;
+                    for(nodeidx=0; nodeidx<=nodescnt-1; nodeidx++)
+                    {
+                        emptyrow = emptyrow && (sparsey && !flagy[srcoffs+nodeidx]);
+                    }
+                    if( emptyrow )
+                    {
+                        continue;
+                    }
+                    
+                    //
+                    // Process row - use either "batch" (rowsize>1) or "generic"
+                    // (row size is 1) algorithm.
+                    //
+                    // NOTE: "generic" version may also be used as fallback code for
+                    //       situations when we do not want to use batch code.
+                    //
+                    maxrowwidth = 0.5*rbfv2nearradius(s.bf, _params)*rcur*s.s[0];
+                    rowwidth = x0[blocks0[i0+1]-1]-x0[blocks0[i0]];
+                    if( nodescnt>1 && (double)(rowwidth)<=(double)(maxrowwidth) )
+                    {
+                        
+                        //
+                        // "Batch" code which processes entire row at once, saving
+                        // some time in kd-tree search code.
+                        //
+                        rquery2 = math.sqr(rcur*rbfv2farradius(s.bf, _params)+0.5*rowwidth/s.s[0]);
+                        rfar2 = math.sqr(rcur*rbfv2farradius(s.bf, _params));
+                        j0 = blocks0[i0];
+                        if( nx>0 )
+                        {
+                            buf.cx[0] = (x0[j0]+0.5*rowwidth)/s.s[0];
+                        }
+                        if( nx>1 )
+                        {
+                            buf.cx[1] = x1[j1]/s.s[1];
+                        }
+                        if( nx>2 )
+                        {
+                            buf.cx[2] = x2[j2]/s.s[2];
+                        }
+                        if( nx>3 )
+                        {
+                            buf.cx[3] = x3[j3]/s.s[3];
+                        }
+                        srcoffs = j0+(j1+(j2+j3*n2)*n1)*n0;
+                        dstoffs = ny*srcoffs;
+                        apserv.rvectorsetlengthatleast(ref buf.rx, nodescnt, _params);
+                        apserv.bvectorsetlengthatleast(ref buf.rf, nodescnt, _params);
+                        apserv.rvectorsetlengthatleast(ref buf.ry, nodescnt*ny, _params);
+                        for(nodeidx=0; nodeidx<=nodescnt-1; nodeidx++)
+                        {
+                            buf.rx[nodeidx] = x0[j0+nodeidx]/s.s[0];
+                            buf.rf[nodeidx] = !sparsey || flagy[srcoffs+nodeidx];
+                        }
+                        for(k=0; k<=nodescnt*ny-1; k++)
+                        {
+                            buf.ry[k] = 0;
+                        }
+                        preparepartialquery(buf.cx, s.kdboxmin, s.kdboxmax, nx, buf.calcbuf, ref dummy, _params);
+                        partialrowcalcrec(s, buf.calcbuf, s.kdroots[levelidx], invrc2, rquery2, rfar2, buf.cx, buf.rx, buf.rf, nodescnt, buf.ry, _params);
+                        for(k=0; k<=nodescnt*ny-1; k++)
+                        {
+                            y[dstoffs+k] = y[dstoffs+k]+buf.ry[k];
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // "Generic" code. Although we usually move here
+                        // only when NodesCnt=1, we still use a loop on
+                        // NodeIdx just to be able to use this branch as
+                        // fallback code without any modifications.
+                        //
+                        rquery2 = math.sqr(rcur*rbfv2farradius(s.bf, _params));
+                        for(nodeidx=0; nodeidx<=nodescnt-1; nodeidx++)
+                        {
+                            
+                            //
+                            // Prepare TX - current point
+                            //
+                            j0 = blocks0[i0]+nodeidx;
+                            if( nx>0 )
+                            {
+                                buf.tx[0] = x0[j0]/s.s[0];
+                            }
+                            if( nx>1 )
+                            {
+                                buf.tx[1] = x1[j1]/s.s[1];
+                            }
+                            if( nx>2 )
+                            {
+                                buf.tx[2] = x2[j2]/s.s[2];
+                            }
+                            if( nx>3 )
+                            {
+                                buf.tx[3] = x3[j3]/s.s[3];
+                            }
+                            
+                            //
+                            // Evaluate and add to Y
+                            //
+                            srcoffs = j0+(j1+(j2+j3*n2)*n1)*n0;
+                            dstoffs = ny*srcoffs;
+                            for(l=0; l<=ny-1; l++)
+                            {
+                                buf.ty[l] = 0;
+                            }
+                            if( !sparsey || flagy[srcoffs] )
+                            {
+                                preparepartialquery(buf.tx, s.kdboxmin, s.kdboxmax, nx, buf.calcbuf, ref dummy, _params);
+                                partialcalcrec(s, buf.calcbuf, s.kdroots[levelidx], invrc2, rquery2, buf.tx, buf.ty, _params);
+                            }
+                            for(l=0; l<=ny-1; l++)
+                            {
+                                y[dstoffs+l] = y[dstoffs+l]+buf.ty[l];
+                            }
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Recycle buffer object back to pool
+            //
+            alglib.smp.ae_shared_pool_recycle(bufpool, ref buf);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_rbfv2partialgridcalcrec(rbfv2model s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            double[] x2,
+            int n2,
+            double[] x3,
+            int n3,
+            int[] blocks0,
+            int block0a,
+            int block0b,
+            int[] blocks1,
+            int block1a,
+            int block1b,
+            int[] blocks2,
+            int block2a,
+            int block2b,
+            int[] blocks3,
+            int block3a,
+            int block3b,
+            bool[] flagy,
+            bool sparsey,
+            int levelidx,
+            double avgfuncpernode,
+            alglib.smp.shared_pool bufpool,
+            double[] y, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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 NX columns   - radii, per dimension
+            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 rbfv2unpack(rbfv2model s,
+            ref int nx,
+            ref int ny,
+            ref double[,] xwr,
+            ref int nc,
+            ref double[,] v,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int ncactual = 0;
+            int i_ = 0;
+
+            nx = 0;
+            ny = 0;
+            xwr = new double[0,0];
+            nc = 0;
+            v = new double[0,0];
+
+            nx = s.nx;
+            ny = s.ny;
+            nc = 0;
+            
+            //
+            // Fill V
+            //
+            v = new double[s.ny, s.nx+1];
+            for(i=0; i<=s.ny-1; i++)
+            {
+                for(i_=0; i_<=s.nx;i_++)
+                {
+                    v[i,i_] = s.v[i,i_];
+                }
+            }
+            
+            //
+            // Fill XWR
+            //
+            alglib.ap.assert(alglib.ap.len(s.cw)%(s.nx+s.ny)==0, "RBFV2Unpack: integrity error");
+            nc = alglib.ap.len(s.cw)/(s.nx+s.ny);
+            ncactual = 0;
+            if( nc>0 )
+            {
+                xwr = new double[nc, s.nx+s.ny+s.nx];
+                for(i=0; i<=s.nh-1; i++)
+                {
+                    partialunpackrec(s.kdnodes, s.kdsplits, s.cw, s.s, s.nx, s.ny, s.kdroots[i], s.ri[i], xwr, ref ncactual, _params);
+                }
+            }
+            alglib.ap.assert(nc==ncactual, "RBFV2Unpack: integrity error");
+        }
+
+
+        private static bool rbfv2buildlinearmodel(double[,] x,
+            ref double[,] y,
+            int n,
+            int nx,
+            int ny,
+            int modeltype,
+            ref double[,] v,
+            alglib.xparams _params)
+        {
+            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(nx>0, "BuildLinearModel: NX<=0");
+            alglib.ap.assert(ny>0, "BuildLinearModel: NY<=0");
+            
+            //
+            // Handle degenerate case (N=0)
+            //
+            result = true;
+            v = new double[ny, nx+1];
+            if( n==0 )
+            {
+                for(j=0; j<=nx; 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, nx+1];
+                shifting = new double[nx];
+                scaling = 0;
+                for(i=0; i<=nx-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<=nx-1; j++)
+                    {
+                        a[i,j] = (x[i,j]-shifting[j])/scaling;
+                    }
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    a[i,nx] = 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, nx+1, ref info, ref c, rep, _params);
+                    if( info<=0 )
+                    {
+                        result = false;
+                        return result;
+                    }
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        v[i,j] = c[j]/scaling;
+                    }
+                    v[i,nx] = c[nx];
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        v[i,nx] = v[i,nx]-shifting[j]*v[i,j];
+                    }
+                    for(j=0; j<=n-1; j++)
+                    {
+                        for(k=0; k<=nx-1; k++)
+                        {
+                            y[j,i] = y[j,i]-x[j,k]*v[i,k];
+                        }
+                        y[j,i] = y[j,i]-v[i,nx];
+                    }
+                }
+                return result;
+            }
+            
+            //
+            // Constant model, very simple
+            //
+            if( modeltype==2 )
+            {
+                for(i=0; i<=ny-1; i++)
+                {
+                    for(j=0; j<=nx; j++)
+                    {
+                        v[i,j] = 0;
+                    }
+                    for(j=0; j<=n-1; j++)
+                    {
+                        v[i,nx] = v[i,nx]+y[j,i];
+                    }
+                    if( n>0 )
+                    {
+                        v[i,nx] = v[i,nx]/n;
+                    }
+                    for(j=0; j<=n-1; j++)
+                    {
+                        y[j,i] = y[j,i]-v[i,nx];
+                    }
+                }
+                return result;
+            }
+            
+            //
+            // Zero model
+            //
+            alglib.ap.assert(modeltype==3, "BuildLinearModel: unknown model type");
+            for(i=0; i<=ny-1; i++)
+            {
+                for(j=0; j<=nx; j++)
+                {
+                    v[i,j] = 0;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Reallocates calcBuf if necessary, reuses previously allocated space if
+        possible.
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Sergey Bochkanov
+        *************************************************************************/
+        private static void allocatecalcbuffer(rbfv2model s,
+            rbfv2calcbuffer buf,
+            alglib.xparams _params)
+        {
+            if( alglib.ap.len(buf.x)<s.nx )
+            {
+                buf.x = new double[s.nx];
+            }
+            if( alglib.ap.len(buf.curboxmin)<s.nx )
+            {
+                buf.curboxmin = new double[s.nx];
+            }
+            if( alglib.ap.len(buf.curboxmax)<s.nx )
+            {
+                buf.curboxmax = new double[s.nx];
+            }
+            if( alglib.ap.len(buf.x123)<s.nx )
+            {
+                buf.x123 = new double[s.nx];
+            }
+            if( alglib.ap.len(buf.y123)<s.ny )
+            {
+                buf.y123 = new double[s.ny];
+            }
+        }
+
+
+        /*************************************************************************
+        Extracts structure (and XY-values too) from  kd-tree  built  for  a  small
+        subset of points and appends it to multi-tree.
+
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Sergey Bochkanov
+        *************************************************************************/
+        private static void convertandappendtree(nearestneighbor.kdtree curtree,
+            int n,
+            int nx,
+            int ny,
+            ref int[] kdnodes,
+            ref double[] kdsplits,
+            ref double[] cw,
+            alglib.xparams _params)
+        {
+            int nodesbase = 0;
+            int splitsbase = 0;
+            int cwbase = 0;
+            int[] localnodes = new int[0];
+            double[] localsplits = new double[0];
+            double[] localcw = new double[0];
+            double[,] xybuf = new double[0,0];
+            int localnodessize = 0;
+            int localsplitssize = 0;
+            int localcwsize = 0;
+            int i = 0;
+
+            
+            //
+            // Calculate base offsets
+            //
+            nodesbase = alglib.ap.len(kdnodes);
+            splitsbase = alglib.ap.len(kdsplits);
+            cwbase = alglib.ap.len(cw);
+            
+            //
+            // Prepare local copy of tree
+            //
+            localnodes = new int[n*maxnodesize];
+            localsplits = new double[n];
+            localcw = new double[(nx+ny)*n];
+            localnodessize = 0;
+            localsplitssize = 0;
+            localcwsize = 0;
+            converttreerec(curtree, n, nx, ny, 0, nodesbase, splitsbase, cwbase, localnodes, ref localnodessize, localsplits, ref localsplitssize, localcw, ref localcwsize, ref xybuf, _params);
+            
+            //
+            // Append to multi-tree
+            //
+            apserv.ivectorresize(ref kdnodes, alglib.ap.len(kdnodes)+localnodessize, _params);
+            apserv.rvectorresize(ref kdsplits, alglib.ap.len(kdsplits)+localsplitssize, _params);
+            apserv.rvectorresize(ref cw, alglib.ap.len(cw)+localcwsize, _params);
+            for(i=0; i<=localnodessize-1; i++)
+            {
+                kdnodes[nodesbase+i] = localnodes[i];
+            }
+            for(i=0; i<=localsplitssize-1; i++)
+            {
+                kdsplits[splitsbase+i] = localsplits[i];
+            }
+            for(i=0; i<=localcwsize-1; i++)
+            {
+                cw[cwbase+i] = localcw[i];
+            }
+        }
+
+
+        /*************************************************************************
+        Recurrent tree conversion
+
+            CurTree         -   tree to convert
+            N, NX, NY       -   dataset metrics
+            NodeOffset      -   offset of current tree node, 0 for root
+            NodesBase       -   a value which is added to intra-tree node indexes;
+                                although this tree is stored in separate array, it
+                                is intended to be stored in the larger tree,  with
+                                localNodes being moved to offset NodesBase.
+            SplitsBase      -   similarly, offset of localSplits in the final tree
+            CWBase          -   similarly, offset of localCW in the final tree
+        *************************************************************************/
+        private static void converttreerec(nearestneighbor.kdtree curtree,
+            int n,
+            int nx,
+            int ny,
+            int nodeoffset,
+            int nodesbase,
+            int splitsbase,
+            int cwbase,
+            int[] localnodes,
+            ref int localnodessize,
+            double[] localsplits,
+            ref int localsplitssize,
+            double[] localcw,
+            ref int localcwsize,
+            ref double[,] xybuf,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int nodetype = 0;
+            int cnt = 0;
+            int d = 0;
+            double s = 0;
+            int nodele = 0;
+            int nodege = 0;
+            int oldnodessize = 0;
+
+            nearestneighbor.kdtreeexplorenodetype(curtree, nodeoffset, ref nodetype, _params);
+            
+            //
+            // Leaf node
+            //
+            if( nodetype==0 )
+            {
+                nearestneighbor.kdtreeexploreleaf(curtree, nodeoffset, ref xybuf, ref cnt, _params);
+                alglib.ap.assert(alglib.ap.len(localnodes)>=localnodessize+2, "ConvertTreeRec: integrity check failed");
+                alglib.ap.assert(alglib.ap.len(localcw)>=localcwsize+cnt*(nx+ny), "ConvertTreeRec: integrity check failed");
+                localnodes[localnodessize+0] = cnt;
+                localnodes[localnodessize+1] = cwbase+localcwsize;
+                localnodessize = localnodessize+2;
+                for(i=0; i<=cnt-1; i++)
+                {
+                    for(j=0; j<=nx+ny-1; j++)
+                    {
+                        localcw[localcwsize+i*(nx+ny)+j] = xybuf[i,j];
+                    }
+                }
+                localcwsize = localcwsize+cnt*(nx+ny);
+                return;
+            }
+            
+            //
+            // Split node
+            //
+            if( nodetype==1 )
+            {
+                nearestneighbor.kdtreeexploresplit(curtree, nodeoffset, ref d, ref s, ref nodele, ref nodege, _params);
+                alglib.ap.assert(alglib.ap.len(localnodes)>=localnodessize+maxnodesize, "ConvertTreeRec: integrity check failed");
+                alglib.ap.assert(alglib.ap.len(localsplits)>=localsplitssize+1, "ConvertTreeRec: integrity check failed");
+                oldnodessize = localnodessize;
+                localnodes[localnodessize+0] = 0;
+                localnodes[localnodessize+1] = d;
+                localnodes[localnodessize+2] = splitsbase+localsplitssize;
+                localnodes[localnodessize+3] = -1;
+                localnodes[localnodessize+4] = -1;
+                localnodessize = localnodessize+5;
+                localsplits[localsplitssize+0] = s;
+                localsplitssize = localsplitssize+1;
+                localnodes[oldnodessize+3] = nodesbase+localnodessize;
+                converttreerec(curtree, n, nx, ny, nodele, nodesbase, splitsbase, cwbase, localnodes, ref localnodessize, localsplits, ref localsplitssize, localcw, ref localcwsize, ref xybuf, _params);
+                localnodes[oldnodessize+4] = nodesbase+localnodessize;
+                converttreerec(curtree, n, nx, ny, nodege, nodesbase, splitsbase, cwbase, localnodes, ref localnodessize, localsplits, ref localsplitssize, localcw, ref localcwsize, ref xybuf, _params);
+                return;
+            }
+            
+            //
+            // Integrity error
+            //
+            alglib.ap.assert(false, "ConvertTreeRec: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This function performs partial calculation of  hierarchical  model:  given
+        evaluation point X and partially computed value Y, it updates Y by  values
+        computed using part of multi-tree given by RootIdx.
+
+        INPUT PARAMETERS:
+            S       -   V2 model
+            Buf     -   calc-buffer, this function uses following fields:
+                        * Buf.CurBoxMin - should be set by caller
+                        * Buf.CurBoxMax - should be set by caller
+                        * Buf.CurDist2  - squared distance from X to current bounding box,
+                          should be set by caller
+            RootIdx -   offset of partial kd-tree
+            InvR2   -   1/R^2, where R is basis function radius
+            QueryR2 -   squared query radius, usually it is (R*FarRadius(BasisFunction))^2
+            X       -   evaluation point, array[NX]
+            Y       -   partial value, array[NY]
+            
+        OUTPUT PARAMETERS
+            Y       -   updated partial value
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static void partialcalcrec(rbfv2model s,
+            rbfv2calcbuffer buf,
+            int rootidx,
+            double invr2,
+            double queryr2,
+            double[] x,
+            double[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double ptdist2 = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            int cwoffs = 0;
+            int cwcnt = 0;
+            int itemoffs = 0;
+            double arg = 0;
+            double val = 0;
+            int d = 0;
+            double split = 0;
+            int childle = 0;
+            int childge = 0;
+            int childoffs = 0;
+            bool updatemin = new bool();
+            double prevdist2 = 0;
+            double t1 = 0;
+            int nx = 0;
+            int ny = 0;
+
+            nx = s.nx;
+            ny = s.ny;
+            
+            //
+            // Helps to avoid spurious warnings
+            //
+            val = 0;
+            
+            //
+            // Leaf node.
+            //
+            if( s.kdnodes[rootidx]>0 )
+            {
+                cwcnt = s.kdnodes[rootidx+0];
+                cwoffs = s.kdnodes[rootidx+1];
+                for(i=0; i<=cwcnt-1; i++)
+                {
+                    
+                    //
+                    // Calculate distance
+                    //
+                    itemoffs = cwoffs+i*(nx+ny);
+                    ptdist2 = 0;
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        v = s.cw[itemoffs+j]-x[j];
+                        ptdist2 = ptdist2+v*v;
+                    }
+                    
+                    //
+                    // Skip points if distance too large
+                    //
+                    if( ptdist2>=queryr2 )
+                    {
+                        continue;
+                    }
+                    
+                    //
+                    // Update Y
+                    //
+                    arg = ptdist2*invr2;
+                    if( s.bf==0 )
+                    {
+                        val = Math.Exp(-arg);
+                    }
+                    else
+                    {
+                        if( s.bf==1 )
+                        {
+                            val = rbfv2basisfunc(s.bf, arg, _params);
+                        }
+                        else
+                        {
+                            alglib.ap.assert(false, "PartialCalcRec: integrity check failed");
+                        }
+                    }
+                    itemoffs = itemoffs+nx;
+                    for(j=0; j<=ny-1; j++)
+                    {
+                        y[j] = y[j]+val*s.cw[itemoffs+j];
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Simple split
+            //
+            if( s.kdnodes[rootidx]==0 )
+            {
+                
+                //
+                // Load:
+                // * D      dimension to split
+                // * Split  split position
+                // * ChildLE, ChildGE - indexes of childs
+                //
+                d = s.kdnodes[rootidx+1];
+                split = s.kdsplits[s.kdnodes[rootidx+2]];
+                childle = s.kdnodes[rootidx+3];
+                childge = s.kdnodes[rootidx+4];
+                
+                //
+                // 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
+                    //
+                    updatemin = i!=0;
+                    if( i==0 )
+                    {
+                        childoffs = childle;
+                    }
+                    else
+                    {
+                        childoffs = childge;
+                    }
+                    
+                    //
+                    // Update bounding box and current distance
+                    //
+                    prevdist2 = buf.curdist2;
+                    t1 = x[d];
+                    if( updatemin )
+                    {
+                        v = buf.curboxmin[d];
+                        if( t1<=split )
+                        {
+                            v0 = v-t1;
+                            if( v0<0 )
+                            {
+                                v0 = 0;
+                            }
+                            v1 = split-t1;
+                            buf.curdist2 = buf.curdist2-v0*v0+v1*v1;
+                        }
+                        buf.curboxmin[d] = split;
+                    }
+                    else
+                    {
+                        v = buf.curboxmax[d];
+                        if( t1>=split )
+                        {
+                            v0 = t1-v;
+                            if( v0<0 )
+                            {
+                                v0 = 0;
+                            }
+                            v1 = t1-split;
+                            buf.curdist2 = buf.curdist2-v0*v0+v1*v1;
+                        }
+                        buf.curboxmax[d] = split;
+                    }
+                    
+                    //
+                    // Decide: to dive into cell or not to dive
+                    //
+                    if( buf.curdist2<queryr2 )
+                    {
+                        partialcalcrec(s, buf, childoffs, invr2, queryr2, x, y, _params);
+                    }
+                    
+                    //
+                    // Restore bounding box and distance
+                    //
+                    if( updatemin )
+                    {
+                        buf.curboxmin[d] = v;
+                    }
+                    else
+                    {
+                        buf.curboxmax[d] = v;
+                    }
+                    buf.curdist2 = prevdist2;
+                }
+                return;
+            }
+            
+            //
+            // Integrity failure
+            //
+            alglib.ap.assert(false, "PartialCalcRec: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This function performs same operation as partialcalcrec(), but  for entire
+        row of the grid. "Row" is a set of nodes (x0,x1,x2,x3) which share x1..x3,
+        but have different x0's. (note: for 2D/3D problems x2..x3 are zero).
+
+        Row is given by:
+        * central point XC, which is located at the center of the row, and used to
+          perform kd-tree requests
+        * set of x0 coordinates stored in RX array (array may be unordered, but it
+          is expected that spread of x0  is  no  more  than  R;  function  may  be
+          inefficient for larger spreads).
+        * set of YFlag values stored in RF
+
+        INPUT PARAMETERS:
+            S       -   V2 model
+            Buf     -   calc-buffer, this function uses following fields:
+                        * Buf.CurBoxMin - should be set by caller
+                        * Buf.CurBoxMax - should be set by caller
+                        * Buf.CurDist2  - squared distance from X to current bounding box,
+                          should be set by caller
+            RootIdx -   offset of partial kd-tree
+            InvR2   -   1/R^2, where R is basis function radius
+            RQuery2 -   squared query radius, usually it is (R*FarRadius(BasisFunction)+0.5*RowWidth)^2,
+                        where RowWidth is its spatial  extent  (after  scaling  of
+                        variables). This radius is used to perform  initial  query
+                        for neighbors of CX.
+            RFar2   -   squared far radius; far radius is used to perform actual
+                        filtering of results of query made with RQuery2.
+            CX      -   central point, array[NX], used for queries
+            RX      -   x0 coordinates, array[RowSize]
+            RF      -   sparsity flags, array[RowSize]
+            RowSize -   row size in elements
+            RY      -   input partial value, array[NY]
+            
+        OUTPUT PARAMETERS
+            RY      -   updated partial value (function adds its results to RY)
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static void partialrowcalcrec(rbfv2model s,
+            rbfv2calcbuffer buf,
+            int rootidx,
+            double invr2,
+            double rquery2,
+            double rfar2,
+            double[] cx,
+            double[] rx,
+            bool[] rf,
+            int rowsize,
+            double[] ry,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int i0 = 0;
+            int i1 = 0;
+            double partialptdist2 = 0;
+            double ptdist2 = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            int cwoffs = 0;
+            int cwcnt = 0;
+            int itemoffs = 0;
+            int woffs = 0;
+            double val = 0;
+            int d = 0;
+            double split = 0;
+            int childle = 0;
+            int childge = 0;
+            int childoffs = 0;
+            bool updatemin = new bool();
+            double prevdist2 = 0;
+            double t1 = 0;
+            int nx = 0;
+            int ny = 0;
+
+            nx = s.nx;
+            ny = s.ny;
+            
+            //
+            // Leaf node.
+            //
+            if( s.kdnodes[rootidx]>0 )
+            {
+                cwcnt = s.kdnodes[rootidx+0];
+                cwoffs = s.kdnodes[rootidx+1];
+                for(i0=0; i0<=cwcnt-1; i0++)
+                {
+                    
+                    //
+                    // Calculate partial distance (components from 1 to NX-1)
+                    //
+                    itemoffs = cwoffs+i0*(nx+ny);
+                    partialptdist2 = 0;
+                    for(j=1; j<=nx-1; j++)
+                    {
+                        v = s.cw[itemoffs+j]-cx[j];
+                        partialptdist2 = partialptdist2+v*v;
+                    }
+                    
+                    //
+                    // Process each element of the row
+                    //
+                    for(i1=0; i1<=rowsize-1; i1++)
+                    {
+                        if( rf[i1] )
+                        {
+                            
+                            //
+                            // Calculate distance
+                            //
+                            v = s.cw[itemoffs]-rx[i1];
+                            ptdist2 = partialptdist2+v*v;
+                            
+                            //
+                            // Skip points if distance too large
+                            //
+                            if( ptdist2>=rfar2 )
+                            {
+                                continue;
+                            }
+                            
+                            //
+                            // Update Y
+                            //
+                            val = rbfv2basisfunc(s.bf, ptdist2*invr2, _params);
+                            woffs = itemoffs+nx;
+                            for(j=0; j<=ny-1; j++)
+                            {
+                                ry[j+i1*ny] = ry[j+i1*ny]+val*s.cw[woffs+j];
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Simple split
+            //
+            if( s.kdnodes[rootidx]==0 )
+            {
+                
+                //
+                // Load:
+                // * D      dimension to split
+                // * Split  split position
+                // * ChildLE, ChildGE - indexes of childs
+                //
+                d = s.kdnodes[rootidx+1];
+                split = s.kdsplits[s.kdnodes[rootidx+2]];
+                childle = s.kdnodes[rootidx+3];
+                childge = s.kdnodes[rootidx+4];
+                
+                //
+                // 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
+                    //
+                    updatemin = i!=0;
+                    if( i==0 )
+                    {
+                        childoffs = childle;
+                    }
+                    else
+                    {
+                        childoffs = childge;
+                    }
+                    
+                    //
+                    // Update bounding box and current distance
+                    //
+                    prevdist2 = buf.curdist2;
+                    t1 = cx[d];
+                    if( updatemin )
+                    {
+                        v = buf.curboxmin[d];
+                        if( t1<=split )
+                        {
+                            v0 = v-t1;
+                            if( v0<0 )
+                            {
+                                v0 = 0;
+                            }
+                            v1 = split-t1;
+                            buf.curdist2 = buf.curdist2-v0*v0+v1*v1;
+                        }
+                        buf.curboxmin[d] = split;
+                    }
+                    else
+                    {
+                        v = buf.curboxmax[d];
+                        if( t1>=split )
+                        {
+                            v0 = t1-v;
+                            if( v0<0 )
+                            {
+                                v0 = 0;
+                            }
+                            v1 = t1-split;
+                            buf.curdist2 = buf.curdist2-v0*v0+v1*v1;
+                        }
+                        buf.curboxmax[d] = split;
+                    }
+                    
+                    //
+                    // Decide: to dive into cell or not to dive
+                    //
+                    if( buf.curdist2<rquery2 )
+                    {
+                        partialrowcalcrec(s, buf, childoffs, invr2, rquery2, rfar2, cx, rx, rf, rowsize, ry, _params);
+                    }
+                    
+                    //
+                    // Restore bounding box and distance
+                    //
+                    if( updatemin )
+                    {
+                        buf.curboxmin[d] = v;
+                    }
+                    else
+                    {
+                        buf.curboxmax[d] = v;
+                    }
+                    buf.curdist2 = prevdist2;
+                }
+                return;
+            }
+            
+            //
+            // Integrity failure
+            //
+            alglib.ap.assert(false, "PartialCalcRec: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This function prepares partial query
+
+        INPUT PARAMETERS:
+            X       -   query point
+            kdBoxMin, kdBoxMax - current bounding box
+            NX      -   problem size
+            Buf     -   preallocated buffer; this function just loads data, but
+                        does not allocate place for them.
+            Cnt     -   counter variable which is set to zery by this function, as
+                        convenience, and to remember about necessity to zero counter
+                        prior to calling partialqueryrec().
+            
+        OUTPUT PARAMETERS
+            Buf     -   calc-buffer:
+                        * Buf.CurBoxMin - current box
+                        * Buf.CurBoxMax - current box
+                        * Buf.CurDist2  - squared distance from X to current box
+            Cnt     -   set to zero
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static void preparepartialquery(double[] x,
+            double[] kdboxmin,
+            double[] kdboxmax,
+            int nx,
+            rbfv2calcbuffer buf,
+            ref int cnt,
+            alglib.xparams _params)
+        {
+            int j = 0;
+
+            cnt = 0;
+            buf.curdist2 = 0;
+            for(j=0; j<=nx-1; j++)
+            {
+                buf.curboxmin[j] = kdboxmin[j];
+                buf.curboxmax[j] = kdboxmax[j];
+                if( (double)(x[j])<(double)(buf.curboxmin[j]) )
+                {
+                    buf.curdist2 = buf.curdist2+math.sqr(buf.curboxmin[j]-x[j]);
+                }
+                else
+                {
+                    if( (double)(x[j])>(double)(buf.curboxmax[j]) )
+                    {
+                        buf.curdist2 = buf.curdist2+math.sqr(x[j]-buf.curboxmax[j]);
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function performs partial (for just one subtree of multi-tree)  query
+        for neighbors located in R-sphere around X. It returns  squared  distances
+        from X to points and offsets in S.CW[] array for points being found.
+
+        INPUT PARAMETERS:
+            kdNodes, kdSplits, CW, NX, NY - corresponding fields of V2 model
+            Buf     -   calc-buffer, this function uses following fields:
+                        * Buf.CurBoxMin - should be set by caller
+                        * Buf.CurBoxMax - should be set by caller
+                        * Buf.CurDist2  - squared distance from X to current
+                          bounding box, should be set by caller
+                        You may use preparepartialquery() function to initialize
+                        these fields.
+            RootIdx -   offset of partial kd-tree
+            QueryR2 -   squared query radius
+            X       -   array[NX], point being queried
+            R2      -   preallocated output buffer; it is caller's responsibility
+                        to make sure that R2 has enough space.
+            Offs    -   preallocated output buffer; it is caller's responsibility
+                        to make sure that Offs has enough space.
+            K       -   MUST BE ZERO ON INITIAL CALL. This variable is incremented,
+                        not set. So, any no-zero value will result in the incorrect
+                        points count being returned.
+            
+        OUTPUT PARAMETERS
+            R2      -   squared distances in first K elements
+            Offs    -   offsets in S.CW in first K elements
+            K       -   points count
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static void partialqueryrec(int[] kdnodes,
+            double[] kdsplits,
+            double[] cw,
+            int nx,
+            int ny,
+            rbfv2calcbuffer buf,
+            int rootidx,
+            double queryr2,
+            double[] x,
+            double[] r2,
+            int[] offs,
+            ref int k,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double ptdist2 = 0;
+            double v = 0;
+            int cwoffs = 0;
+            int cwcnt = 0;
+            int itemoffs = 0;
+            int d = 0;
+            double split = 0;
+            int childle = 0;
+            int childge = 0;
+            int childoffs = 0;
+            bool updatemin = new bool();
+            double prevdist2 = 0;
+            double t1 = 0;
+
+            
+            //
+            // Leaf node.
+            //
+            if( kdnodes[rootidx]>0 )
+            {
+                cwcnt = kdnodes[rootidx+0];
+                cwoffs = kdnodes[rootidx+1];
+                for(i=0; i<=cwcnt-1; i++)
+                {
+                    
+                    //
+                    // Calculate distance
+                    //
+                    itemoffs = cwoffs+i*(nx+ny);
+                    ptdist2 = 0;
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        v = cw[itemoffs+j]-x[j];
+                        ptdist2 = ptdist2+v*v;
+                    }
+                    
+                    //
+                    // Skip points if distance too large
+                    //
+                    if( (double)(ptdist2)>=(double)(queryr2) )
+                    {
+                        continue;
+                    }
+                    
+                    //
+                    // Output
+                    //
+                    r2[k] = ptdist2;
+                    offs[k] = itemoffs;
+                    k = k+1;
+                }
+                return;
+            }
+            
+            //
+            // Simple split
+            //
+            if( kdnodes[rootidx]==0 )
+            {
+                
+                //
+                // Load:
+                // * D      dimension to split
+                // * Split  split position
+                // * ChildLE, ChildGE - indexes of childs
+                //
+                d = kdnodes[rootidx+1];
+                split = kdsplits[kdnodes[rootidx+2]];
+                childle = kdnodes[rootidx+3];
+                childge = kdnodes[rootidx+4];
+                
+                //
+                // 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
+                    //
+                    updatemin = i!=0;
+                    if( i==0 )
+                    {
+                        childoffs = childle;
+                    }
+                    else
+                    {
+                        childoffs = childge;
+                    }
+                    
+                    //
+                    // Update bounding box and current distance
+                    //
+                    prevdist2 = buf.curdist2;
+                    t1 = x[d];
+                    if( updatemin )
+                    {
+                        v = buf.curboxmin[d];
+                        if( (double)(t1)<=(double)(split) )
+                        {
+                            buf.curdist2 = buf.curdist2-math.sqr(Math.Max(v-t1, 0))+math.sqr(split-t1);
+                        }
+                        buf.curboxmin[d] = split;
+                    }
+                    else
+                    {
+                        v = buf.curboxmax[d];
+                        if( (double)(t1)>=(double)(split) )
+                        {
+                            buf.curdist2 = buf.curdist2-math.sqr(Math.Max(t1-v, 0))+math.sqr(t1-split);
+                        }
+                        buf.curboxmax[d] = split;
+                    }
+                    
+                    //
+                    // Decide: to dive into cell or not to dive
+                    //
+                    if( (double)(buf.curdist2)<(double)(queryr2) )
+                    {
+                        partialqueryrec(kdnodes, kdsplits, cw, nx, ny, buf, childoffs, queryr2, x, r2, offs, ref k, _params);
+                    }
+                    
+                    //
+                    // Restore bounding box and distance
+                    //
+                    if( updatemin )
+                    {
+                        buf.curboxmin[d] = v;
+                    }
+                    else
+                    {
+                        buf.curboxmax[d] = v;
+                    }
+                    buf.curdist2 = prevdist2;
+                }
+                return;
+            }
+            
+            //
+            // Integrity failure
+            //
+            alglib.ap.assert(false, "PartialQueryRec: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This function performs  partial  (for  just  one  subtree  of  multi-tree)
+        counting of neighbors located in R-sphere around X.
+
+        This function does not guarantee consistency of results with other partial
+        queries, it should be used only to get approximate estimates (well, we  do
+        not  use   approximate   algorithms,  but  rounding  errors  may  give  us
+        inconsistent results in just-at-the-boundary cases).
+
+        INPUT PARAMETERS:
+            kdNodes, kdSplits, CW, NX, NY - corresponding fields of V2 model
+            Buf     -   calc-buffer, this function uses following fields:
+                        * Buf.CurBoxMin - should be set by caller
+                        * Buf.CurBoxMax - should be set by caller
+                        * Buf.CurDist2  - squared distance from X to current
+                          bounding box, should be set by caller
+                        You may use preparepartialquery() function to initialize
+                        these fields.
+            RootIdx -   offset of partial kd-tree
+            QueryR2 -   squared query radius
+            X       -   array[NX], point being queried
+            
+        RESULT:
+            points count
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static int partialcountrec(int[] kdnodes,
+            double[] kdsplits,
+            double[] cw,
+            int nx,
+            int ny,
+            rbfv2calcbuffer buf,
+            int rootidx,
+            double queryr2,
+            double[] x,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+            int j = 0;
+            double ptdist2 = 0;
+            double v = 0;
+            int cwoffs = 0;
+            int cwcnt = 0;
+            int itemoffs = 0;
+            int d = 0;
+            double split = 0;
+            int childle = 0;
+            int childge = 0;
+            int childoffs = 0;
+            bool updatemin = new bool();
+            double prevdist2 = 0;
+            double t1 = 0;
+
+            result = 0;
+            
+            //
+            // Leaf node.
+            //
+            if( kdnodes[rootidx]>0 )
+            {
+                cwcnt = kdnodes[rootidx+0];
+                cwoffs = kdnodes[rootidx+1];
+                for(i=0; i<=cwcnt-1; i++)
+                {
+                    
+                    //
+                    // Calculate distance
+                    //
+                    itemoffs = cwoffs+i*(nx+ny);
+                    ptdist2 = 0;
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        v = cw[itemoffs+j]-x[j];
+                        ptdist2 = ptdist2+v*v;
+                    }
+                    
+                    //
+                    // Skip points if distance too large
+                    //
+                    if( (double)(ptdist2)>=(double)(queryr2) )
+                    {
+                        continue;
+                    }
+                    
+                    //
+                    // Output
+                    //
+                    result = result+1;
+                }
+                return result;
+            }
+            
+            //
+            // Simple split
+            //
+            if( kdnodes[rootidx]==0 )
+            {
+                
+                //
+                // Load:
+                // * D      dimension to split
+                // * Split  split position
+                // * ChildLE, ChildGE - indexes of childs
+                //
+                d = kdnodes[rootidx+1];
+                split = kdsplits[kdnodes[rootidx+2]];
+                childle = kdnodes[rootidx+3];
+                childge = kdnodes[rootidx+4];
+                
+                //
+                // 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
+                    //
+                    updatemin = i!=0;
+                    if( i==0 )
+                    {
+                        childoffs = childle;
+                    }
+                    else
+                    {
+                        childoffs = childge;
+                    }
+                    
+                    //
+                    // Update bounding box and current distance
+                    //
+                    prevdist2 = buf.curdist2;
+                    t1 = x[d];
+                    if( updatemin )
+                    {
+                        v = buf.curboxmin[d];
+                        if( (double)(t1)<=(double)(split) )
+                        {
+                            buf.curdist2 = buf.curdist2-math.sqr(Math.Max(v-t1, 0))+math.sqr(split-t1);
+                        }
+                        buf.curboxmin[d] = split;
+                    }
+                    else
+                    {
+                        v = buf.curboxmax[d];
+                        if( (double)(t1)>=(double)(split) )
+                        {
+                            buf.curdist2 = buf.curdist2-math.sqr(Math.Max(t1-v, 0))+math.sqr(t1-split);
+                        }
+                        buf.curboxmax[d] = split;
+                    }
+                    
+                    //
+                    // Decide: to dive into cell or not to dive
+                    //
+                    if( (double)(buf.curdist2)<(double)(queryr2) )
+                    {
+                        result = result+partialcountrec(kdnodes, kdsplits, cw, nx, ny, buf, childoffs, queryr2, x, _params);
+                    }
+                    
+                    //
+                    // Restore bounding box and distance
+                    //
+                    if( updatemin )
+                    {
+                        buf.curboxmin[d] = v;
+                    }
+                    else
+                    {
+                        buf.curboxmax[d] = v;
+                    }
+                    buf.curdist2 = prevdist2;
+                }
+                return result;
+            }
+            
+            //
+            // Integrity failure
+            //
+            alglib.ap.assert(false, "PartialCountRec: integrity check failed");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function performs partial (for just one subtree of multi-tree) unpack
+        for RBF model. It appends center coordinates,  weights  and  per-dimension
+        radii (according to current scaling) to preallocated output array.
+
+        INPUT PARAMETERS:
+            kdNodes, kdSplits, CW, S, NX, NY - corresponding fields of V2 model
+            RootIdx -   offset of partial kd-tree
+            R       -   radius for current partial tree
+            XWR     -   preallocated output buffer; it is caller's responsibility
+                        to make sure that XWR has enough space. First K rows are
+                        already occupied.
+            K       -   number of already occupied rows in XWR.
+            
+        OUTPUT PARAMETERS
+            XWR     -   updated XWR
+            K       -   updated rows count
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static void partialunpackrec(int[] kdnodes,
+            double[] kdsplits,
+            double[] cw,
+            double[] s,
+            int nx,
+            int ny,
+            int rootidx,
+            double r,
+            double[,] xwr,
+            ref int k,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int childle = 0;
+            int childge = 0;
+            int itemoffs = 0;
+            int cwoffs = 0;
+            int cwcnt = 0;
+
+            
+            //
+            // Leaf node.
+            //
+            if( kdnodes[rootidx]>0 )
+            {
+                cwcnt = kdnodes[rootidx+0];
+                cwoffs = kdnodes[rootidx+1];
+                for(i=0; i<=cwcnt-1; i++)
+                {
+                    itemoffs = cwoffs+i*(nx+ny);
+                    for(j=0; j<=nx+ny-1; j++)
+                    {
+                        xwr[k,j] = cw[itemoffs+j];
+                    }
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        xwr[k,j] = xwr[k,j]*s[j];
+                    }
+                    for(j=0; j<=nx-1; j++)
+                    {
+                        xwr[k,nx+ny+j] = r*s[j];
+                    }
+                    k = k+1;
+                }
+                return;
+            }
+            
+            //
+            // Simple split
+            //
+            if( kdnodes[rootidx]==0 )
+            {
+                
+                //
+                // Load:
+                // * ChildLE, ChildGE - indexes of childs
+                //
+                childle = kdnodes[rootidx+3];
+                childge = kdnodes[rootidx+4];
+                
+                //
+                // Process both parts of split
+                //
+                partialunpackrec(kdnodes, kdsplits, cw, s, nx, ny, childle, r, xwr, ref k, _params);
+                partialunpackrec(kdnodes, kdsplits, cw, s, nx, ny, childge, r, xwr, ref k, _params);
+                return;
+            }
+            
+            //
+            // Integrity failure
+            //
+            alglib.ap.assert(false, "PartialUnpackRec: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This function returns size of design matrix row for evaluation point X0,
+        given:
+        * query radius multiplier (either RBFV2NearRadius() or RBFV2FarRadius())
+        * hierarchy level: value in [0,NH) for single-level model, or negative
+          value for multilevel model (all levels of hierarchy in single matrix,
+          like one used by nonnegative RBF)
+
+        INPUT PARAMETERS:
+            kdNodes, kdSplits, CW, Ri, kdRoots, kdBoxMin, kdBoxMax, NX, NY, NH - corresponding fields of V2 model
+            Level   -   value in [0,NH) for single-level design matrix, negative
+                        value for multilevel design matrix
+            RCoeff  -   radius coefficient, either RBFV2NearRadius() or RBFV2FarRadius()
+            X0      -   query point
+            CalcBuf -   buffer for PreparePartialQuery(), allocated by caller
+            
+        RESULT:
+            row size
+
+          -- ALGLIB --
+             Copyright 28.09.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static int designmatrixrowsize(int[] kdnodes,
+            double[] kdsplits,
+            double[] cw,
+            double[] ri,
+            int[] kdroots,
+            double[] kdboxmin,
+            double[] kdboxmax,
+            int nx,
+            int ny,
+            int nh,
+            int level,
+            double rcoeff,
+            double[] x0,
+            rbfv2calcbuffer calcbuf,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int dummy = 0;
+            int levelidx = 0;
+            int level0 = 0;
+            int level1 = 0;
+            double curradius2 = 0;
+
+            alglib.ap.assert(nh>0, "DesignMatrixRowSize: integrity failure");
+            if( level>=0 )
+            {
+                level0 = level;
+                level1 = level;
+            }
+            else
+            {
+                level0 = 0;
+                level1 = nh-1;
+            }
+            result = 0;
+            for(levelidx=level0; levelidx<=level1; levelidx++)
+            {
+                curradius2 = math.sqr(ri[levelidx]*rcoeff);
+                preparepartialquery(x0, kdboxmin, kdboxmax, nx, calcbuf, ref dummy, _params);
+                result = result+partialcountrec(kdnodes, kdsplits, cw, nx, ny, calcbuf, kdroots[levelidx], curradius2, x0, _params);
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function generates design matrix row for evaluation point X0, given:
+        * query radius multiplier (either RBFV2NearRadius() or RBFV2FarRadius())
+        * hierarchy level: value in [0,NH) for single-level model, or negative
+          value for multilevel model (all levels of hierarchy in single matrix,
+          like one used by nonnegative RBF)
+
+        INPUT PARAMETERS:
+            kdNodes, kdSplits, CW, Ri, kdRoots, kdBoxMin, kdBoxMax, NX, NY, NH - corresponding fields of V2 model
+
+            CWRange -   internal array[NH+1] used by RBF construction function,
+                        stores ranges of CW occupied by NH trees.
+            Level   -   value in [0,NH) for single-level design matrix, negative
+                        value for multilevel design matrix
+            BF      -   basis function type
+            RCoeff  -   radius coefficient, either RBFV2NearRadius() or RBFV2FarRadius()
+            RowsPerPoint-equal to:
+                        * 1 for unpenalized regression model
+                        * 1+NX for basic form of nonsmoothness penalty
+            Penalty -   nonsmoothness penalty coefficient
+            
+            X0      -   query point
+            
+            CalcBuf -   buffer for PreparePartialQuery(), allocated by caller
+            R2      -   preallocated temporary buffer, size is at least NPoints;
+                        it is caller's responsibility to make sure that R2 has enough space.
+            Offs    -   preallocated temporary buffer; size is at least NPoints;
+                        it is caller's responsibility to make sure that Offs has enough space.
+            K       -   MUST BE ZERO ON INITIAL CALL. This variable is incremented,
+                        not set. So, any no-zero value will result in the incorrect
+                        points count being returned.
+            RowIdx  -   preallocated array, at least RowSize elements
+            RowVal  -   preallocated array, at least RowSize*RowsPerPoint elements
+            
+        RESULT:
+            RowIdx  -   RowSize elements are filled with column indexes of non-zero
+                        design matrix entries
+            RowVal  -   RowSize*RowsPerPoint elements are filled with design matrix
+                        values, with column RowIdx[0] being stored in first RowsPerPoint
+                        elements of RowVal, column RowIdx[1] being stored in next
+                        RowsPerPoint elements, and so on.
+                        
+                        First element in contiguous set of RowsPerPoint elements
+                        corresponds to 
+                        
+            RowSize -   number of columns per row
+
+          -- ALGLIB --
+             Copyright 28.09.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static void designmatrixgeneraterow(int[] kdnodes,
+            double[] kdsplits,
+            double[] cw,
+            double[] ri,
+            int[] kdroots,
+            double[] kdboxmin,
+            double[] kdboxmax,
+            int[] cwrange,
+            int nx,
+            int ny,
+            int nh,
+            int level,
+            int bf,
+            double rcoeff,
+            int rowsperpoint,
+            double penalty,
+            double[] x0,
+            rbfv2calcbuffer calcbuf,
+            double[] tmpr2,
+            int[] tmpoffs,
+            int[] rowidx,
+            double[] rowval,
+            ref int rowsize,
+            alglib.xparams _params)
+        {
+            int j = 0;
+            int k = 0;
+            int cnt = 0;
+            int levelidx = 0;
+            int level0 = 0;
+            int level1 = 0;
+            double invri2 = 0;
+            double curradius2 = 0;
+            double val = 0;
+            double dval = 0;
+            double d2val = 0;
+
+            rowsize = 0;
+
+            alglib.ap.assert(nh>0, "DesignMatrixGenerateRow: integrity failure (a)");
+            alglib.ap.assert(rowsperpoint==1 || rowsperpoint==1+nx, "DesignMatrixGenerateRow: integrity failure (b)");
+            if( level>=0 )
+            {
+                level0 = level;
+                level1 = level;
+            }
+            else
+            {
+                level0 = 0;
+                level1 = nh-1;
+            }
+            rowsize = 0;
+            for(levelidx=level0; levelidx<=level1; levelidx++)
+            {
+                curradius2 = math.sqr(ri[levelidx]*rcoeff);
+                invri2 = 1/math.sqr(ri[levelidx]);
+                preparepartialquery(x0, kdboxmin, kdboxmax, nx, calcbuf, ref cnt, _params);
+                partialqueryrec(kdnodes, kdsplits, cw, nx, ny, calcbuf, kdroots[levelidx], curradius2, x0, tmpr2, tmpoffs, ref cnt, _params);
+                alglib.ap.assert(alglib.ap.len(tmpr2)>=cnt, "DesignMatrixRowSize: integrity failure (c)");
+                alglib.ap.assert(alglib.ap.len(tmpoffs)>=cnt, "DesignMatrixRowSize: integrity failure (d)");
+                alglib.ap.assert(alglib.ap.len(rowidx)>=rowsize+cnt, "DesignMatrixRowSize: integrity failure (e)");
+                alglib.ap.assert(alglib.ap.len(rowval)>=rowsperpoint*(rowsize+cnt), "DesignMatrixRowSize: integrity failure (f)");
+                for(j=0; j<=cnt-1; j++)
+                {
+                    
+                    //
+                    // Generate element corresponding to fitting error.
+                    // Store derivative information which may be required later.
+                    //
+                    alglib.ap.assert((tmpoffs[j]-cwrange[level0])%(nx+ny)==0, "DesignMatrixRowSize: integrity failure (g)");
+                    rbfv2basisfuncdiff2(bf, tmpr2[j]*invri2, ref val, ref dval, ref d2val, _params);
+                    rowidx[rowsize+j] = (tmpoffs[j]-cwrange[level0])/(nx+ny);
+                    rowval[(rowsize+j)*rowsperpoint+0] = val;
+                    if( rowsperpoint==1 )
+                    {
+                        continue;
+                    }
+                    
+                    //
+                    // Generate elements corresponding to nonsmoothness penalty
+                    //
+                    alglib.ap.assert(rowsperpoint==1+nx, "DesignMatrixRowSize: integrity failure (h)");
+                    for(k=0; k<=nx-1; k++)
+                    {
+                        rowval[(rowsize+j)*rowsperpoint+1+k] = penalty*(dval*2*invri2+d2val*math.sqr(2*(x0[k]-cw[tmpoffs[j]+k])*invri2));
+                    }
+                }
+                
+                //
+                // Update columns counter
+                //
+                rowsize = rowsize+cnt;
+            }
+        }
+
+
+        /*************************************************************************
+        This function fills RBF model by zeros.
+
+          -- ALGLIB --
+             Copyright 17.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void zerofill(rbfv2model s,
+            int nx,
+            int ny,
+            int bf,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            s.bf = bf;
+            s.nh = 0;
+            s.ri = new double[0];
+            s.s = new double[0];
+            s.kdroots = new int[0];
+            s.kdnodes = new int[0];
+            s.kdsplits = new double[0];
+            s.kdboxmin = new double[0];
+            s.kdboxmax = new double[0];
+            s.cw = new double[0];
+            s.v = new double[ny, nx+1];
+            for(i=0; i<=ny-1; i++)
+            {
+                for(j=0; j<=nx; j++)
+                {
+                    s.v[i,j] = 0;
+                }
+            }
+        }
+
+
+    }
+    public class spline2d
+    {
+        /*************************************************************************
+        2-dimensional spline inteprolant
+        *************************************************************************/
+        public class spline2dinterpolant : apobject
+        {
+            public int stype;
+            public int n;
+            public int m;
+            public int d;
+            public double[] x;
+            public double[] y;
+            public double[] f;
+            public spline2dinterpolant()
+            {
+                init();
+            }
+            public override void init()
+            {
+                x = new double[0];
+                y = new double[0];
+                f = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                spline2dinterpolant _result = new spline2dinterpolant();
+                _result.stype = stype;
+                _result.n = n;
+                _result.m = m;
+                _result.d = d;
+                _result.x = (double[])x.Clone();
+                _result.y = (double[])y.Clone();
+                _result.f = (double[])f.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Nonlinear least squares solver used to fit 2D splines to data
+        *************************************************************************/
+        public class spline2dbuilder : apobject
+        {
+            public int priorterm;
+            public double priortermval;
+            public int areatype;
+            public double xa;
+            public double xb;
+            public double ya;
+            public double yb;
+            public int gridtype;
+            public int kx;
+            public int ky;
+            public double smoothing;
+            public int nlayers;
+            public int solvertype;
+            public double lambdabase;
+            public double[] xy;
+            public int npoints;
+            public int d;
+            public double sx;
+            public double sy;
+            public bool adddegreeoffreedom;
+            public int interfacesize;
+            public int lsqrcnt;
+            public int maxcoresize;
+            public spline2dbuilder()
+            {
+                init();
+            }
+            public override void init()
+            {
+                xy = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                spline2dbuilder _result = new spline2dbuilder();
+                _result.priorterm = priorterm;
+                _result.priortermval = priortermval;
+                _result.areatype = areatype;
+                _result.xa = xa;
+                _result.xb = xb;
+                _result.ya = ya;
+                _result.yb = yb;
+                _result.gridtype = gridtype;
+                _result.kx = kx;
+                _result.ky = ky;
+                _result.smoothing = smoothing;
+                _result.nlayers = nlayers;
+                _result.solvertype = solvertype;
+                _result.lambdabase = lambdabase;
+                _result.xy = (double[])xy.Clone();
+                _result.npoints = npoints;
+                _result.d = d;
+                _result.sx = sx;
+                _result.sy = sy;
+                _result.adddegreeoffreedom = adddegreeoffreedom;
+                _result.interfacesize = interfacesize;
+                _result.lsqrcnt = lsqrcnt;
+                _result.maxcoresize = maxcoresize;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Spline 2D fitting report:
+            rmserror        RMS error
+            avgerror        average error
+            maxerror        maximum error
+            r2              coefficient of determination,  R-squared, 1-RSS/TSS
+        *************************************************************************/
+        public class spline2dfitreport : apobject
+        {
+            public double rmserror;
+            public double avgerror;
+            public double maxerror;
+            public double r2;
+            public spline2dfitreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                spline2dfitreport _result = new spline2dfitreport();
+                _result.rmserror = rmserror;
+                _result.avgerror = avgerror;
+                _result.maxerror = maxerror;
+                _result.r2 = r2;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Design matrix stored in batch/block sparse format.
+
+        The idea is that design matrix for bicubic spline fitting has very regular
+        structure:
+
+        1. I-th row has non-zero entries in elements with indexes starting
+        from some IDX, and including: IDX, IDX+1, IDX+2, IDX+3, IDX+KX+0, IDX+KX+1,
+        and so on, up to 16 elements in total.
+
+        Rows corresponding to dataset points have 16 non-zero elements, rows
+        corresponding to nonlinearity penalty have 9 non-zero elements, and rows
+        of regularizer have 1 element.
+
+        For the sake of simplicity, we can use 16 elements for dataset rows and
+        penalty rows, and process regularizer explicitly.
+
+        2. points located in the same cell of the grid have same pattern of non-zeros,
+        so we can use dense Level 2 and Level 3 linear algebra to work with such
+        matrices.
+        *************************************************************************/
+        public class spline2dxdesignmatrix : apobject
+        {
+            public int blockwidth;
+            public int kx;
+            public int ky;
+            public int npoints;
+            public int nrows;
+            public int ndenserows;
+            public int ndensebatches;
+            public int d;
+            public int maxbatch;
+            public double[,] vals;
+            public int[] batches;
+            public int[] batchbases;
+            public double lambdareg;
+            public double[] tmp0;
+            public double[] tmp1;
+            public double[,] tmp2;
+            public spline2dxdesignmatrix()
+            {
+                init();
+            }
+            public override void init()
+            {
+                vals = new double[0,0];
+                batches = new int[0];
+                batchbases = new int[0];
+                tmp0 = new double[0];
+                tmp1 = new double[0];
+                tmp2 = new double[0,0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                spline2dxdesignmatrix _result = new spline2dxdesignmatrix();
+                _result.blockwidth = blockwidth;
+                _result.kx = kx;
+                _result.ky = ky;
+                _result.npoints = npoints;
+                _result.nrows = nrows;
+                _result.ndenserows = ndenserows;
+                _result.ndensebatches = ndensebatches;
+                _result.d = d;
+                _result.maxbatch = maxbatch;
+                _result.vals = (double[,])vals.Clone();
+                _result.batches = (int[])batches.Clone();
+                _result.batchbases = (int[])batchbases.Clone();
+                _result.lambdareg = lambdareg;
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmp1 = (double[])tmp1.Clone();
+                _result.tmp2 = (double[,])tmp2.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Temporaries for BlockLLS solver
+        *************************************************************************/
+        public class spline2dblockllsbuf : apobject
+        {
+            public linlsqr.linlsqrstate solver;
+            public linlsqr.linlsqrreport solverrep;
+            public double[,] blockata;
+            public double[,] trsmbuf2;
+            public double[,] cholbuf2;
+            public double[] cholbuf1;
+            public double[] tmp0;
+            public double[] tmp1;
+            public spline2dblockllsbuf()
+            {
+                init();
+            }
+            public override void init()
+            {
+                solver = new linlsqr.linlsqrstate();
+                solverrep = new linlsqr.linlsqrreport();
+                blockata = new double[0,0];
+                trsmbuf2 = new double[0,0];
+                cholbuf2 = new double[0,0];
+                cholbuf1 = new double[0];
+                tmp0 = new double[0];
+                tmp1 = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                spline2dblockllsbuf _result = new spline2dblockllsbuf();
+                _result.solver = (linlsqr.linlsqrstate)solver.make_copy();
+                _result.solverrep = (linlsqr.linlsqrreport)solverrep.make_copy();
+                _result.blockata = (double[,])blockata.Clone();
+                _result.trsmbuf2 = (double[,])trsmbuf2.Clone();
+                _result.cholbuf2 = (double[,])cholbuf2.Clone();
+                _result.cholbuf1 = (double[])cholbuf1.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmp1 = (double[])tmp1.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Temporaries for FastDDM solver
+        *************************************************************************/
+        public class spline2dfastddmbuf : apobject
+        {
+            public spline2dxdesignmatrix xdesignmatrix;
+            public double[] tmp0;
+            public double[] tmpz;
+            public spline2dfitreport dummyrep;
+            public spline2dinterpolant localmodel;
+            public spline2dblockllsbuf blockllsbuf;
+            public spline2dfastddmbuf()
+            {
+                init();
+            }
+            public override void init()
+            {
+                xdesignmatrix = new spline2dxdesignmatrix();
+                tmp0 = new double[0];
+                tmpz = new double[0];
+                dummyrep = new spline2dfitreport();
+                localmodel = new spline2dinterpolant();
+                blockllsbuf = new spline2dblockllsbuf();
+            }
+            public override alglib.apobject make_copy()
+            {
+                spline2dfastddmbuf _result = new spline2dfastddmbuf();
+                _result.xdesignmatrix = (spline2dxdesignmatrix)xdesignmatrix.make_copy();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmpz = (double[])tmpz.Clone();
+                _result.dummyrep = (spline2dfitreport)dummyrep.make_copy();
+                _result.localmodel = (spline2dinterpolant)localmodel.make_copy();
+                _result.blockllsbuf = (spline2dblockllsbuf)blockllsbuf.make_copy();
+                return _result;
+            }
+        };
+
+
+
+
+        public const double cholreg = 1.0E-12;
+        public const double lambdaregblocklls = 1.0E-6;
+        public const double lambdaregfastddm = 1.0E-4;
+        public const double lambdadecay = 0.5;
+
+
+        /*************************************************************************
+        This subroutine calculates the value of the bilinear or bicubic spline  at
+        the given point X.
+
+        Input parameters:
+            C   -   2D spline object.
+                    Built by spline2dbuildbilinearv or spline2dbuildbicubicv.
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int ix = 0;
+            int iy = 0;
+            int l = 0;
+            int r = 0;
+            int h = 0;
+            double t = 0;
+            double dt = 0;
+            double u = 0;
+            double du = 0;
+            double y1 = 0;
+            double y2 = 0;
+            double y3 = 0;
+            double y4 = 0;
+            int s1 = 0;
+            int s2 = 0;
+            int s3 = 0;
+            int s4 = 0;
+            int sfx = 0;
+            int sfy = 0;
+            int sfxy = 0;
+            double t2 = 0;
+            double t3 = 0;
+            double u2 = 0;
+            double u3 = 0;
+            double ht00 = 0;
+            double ht01 = 0;
+            double ht10 = 0;
+            double ht11 = 0;
+            double hu00 = 0;
+            double hu01 = 0;
+            double hu10 = 0;
+            double hu11 = 0;
+
+            alglib.ap.assert(c.stype==-1 || c.stype==-3, "Spline2DCalc: incorrect C (incorrect parameter C.SType)");
+            alglib.ap.assert(math.isfinite(x) && math.isfinite(y), "Spline2DCalc: X or Y contains NaN or Infinite value");
+            if( c.d!=1 )
+            {
+                result = 0;
+                return result;
+            }
+            
+            //
+            // Determine evaluation interval
+            //
+            l = 0;
+            r = c.n-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.x[h])>=(double)(x) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            dt = 1.0/(c.x[l+1]-c.x[l]);
+            t = (x-c.x[l])*dt;
+            ix = l;
+            l = 0;
+            r = c.m-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.y[h])>=(double)(y) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            du = 1.0/(c.y[l+1]-c.y[l]);
+            u = (y-c.y[l])*du;
+            iy = l;
+            
+            //
+            // Bilinear interpolation
+            //
+            if( c.stype==-1 )
+            {
+                y1 = c.f[c.n*iy+ix];
+                y2 = c.f[c.n*iy+(ix+1)];
+                y3 = c.f[c.n*(iy+1)+(ix+1)];
+                y4 = c.f[c.n*(iy+1)+ix];
+                result = (1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4;
+                return result;
+            }
+            
+            //
+            // Bicubic interpolation:
+            // * calculate Hermite basis for dimensions X and Y (variables T and U),
+            //   here HTij means basis function whose I-th derivative has value 1 at T=J.
+            //   Same for HUij.
+            // * after initial calculation, apply scaling by DT/DU to the basis
+            // * calculate using stored table of second derivatives
+            //
+            alglib.ap.assert(c.stype==-3, "Spline2DCalc: integrity check failed");
+            sfx = c.n*c.m;
+            sfy = 2*c.n*c.m;
+            sfxy = 3*c.n*c.m;
+            s1 = c.n*iy+ix;
+            s2 = c.n*iy+(ix+1);
+            s3 = c.n*(iy+1)+ix;
+            s4 = c.n*(iy+1)+(ix+1);
+            t2 = t*t;
+            t3 = t*t2;
+            u2 = u*u;
+            u3 = u*u2;
+            ht00 = 2*t3-3*t2+1;
+            ht10 = t3-2*t2+t;
+            ht01 = -(2*t3)+3*t2;
+            ht11 = t3-t2;
+            hu00 = 2*u3-3*u2+1;
+            hu10 = u3-2*u2+u;
+            hu01 = -(2*u3)+3*u2;
+            hu11 = u3-u2;
+            ht10 = ht10/dt;
+            ht11 = ht11/dt;
+            hu10 = hu10/du;
+            hu11 = hu11/du;
+            result = 0;
+            result = result+c.f[s1]*ht00*hu00+c.f[s2]*ht01*hu00+c.f[s3]*ht00*hu01+c.f[s4]*ht01*hu01;
+            result = result+c.f[sfx+s1]*ht10*hu00+c.f[sfx+s2]*ht11*hu00+c.f[sfx+s3]*ht10*hu01+c.f[sfx+s4]*ht11*hu01;
+            result = result+c.f[sfy+s1]*ht00*hu10+c.f[sfy+s2]*ht01*hu10+c.f[sfy+s3]*ht00*hu11+c.f[sfy+s4]*ht01*hu11;
+            result = result+c.f[sfxy+s1]*ht10*hu10+c.f[sfxy+s2]*ht11*hu10+c.f[sfxy+s3]*ht10*hu11+c.f[sfxy+s4]*ht11*hu11;
+            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,
+            alglib.xparams _params)
+        {
+            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 s1 = 0;
+            int s2 = 0;
+            int s3 = 0;
+            int s4 = 0;
+            int sfx = 0;
+            int sfy = 0;
+            int sfxy = 0;
+            double y1 = 0;
+            double y2 = 0;
+            double y3 = 0;
+            double y4 = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double v3 = 0;
+            double t2 = 0;
+            double t3 = 0;
+            double u2 = 0;
+            double u3 = 0;
+            double ht00 = 0;
+            double ht01 = 0;
+            double ht10 = 0;
+            double ht11 = 0;
+            double hu00 = 0;
+            double hu01 = 0;
+            double hu10 = 0;
+            double hu11 = 0;
+            double dht00 = 0;
+            double dht01 = 0;
+            double dht10 = 0;
+            double dht11 = 0;
+            double dhu00 = 0;
+            double dhu01 = 0;
+            double dhu10 = 0;
+            double dhu11 = 0;
+
+            f = 0;
+            fx = 0;
+            fy = 0;
+            fxy = 0;
+
+            alglib.ap.assert(c.stype==-1 || c.stype==-3, "Spline2DDiff: incorrect C (incorrect parameter C.SType)");
+            alglib.ap.assert(math.isfinite(x) && math.isfinite(y), "Spline2DDiff: X or Y contains NaN or Infinite value");
+            
+            //
+            // Prepare F, dF/dX, dF/dY, d2F/dXdY
+            //
+            f = 0;
+            fx = 0;
+            fy = 0;
+            fxy = 0;
+            if( c.d!=1 )
+            {
+                return;
+            }
+            
+            //
+            // Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
+            //
+            l = 0;
+            r = c.n-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.x[h])>=(double)(x) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            t = (x-c.x[l])/(c.x[l+1]-c.x[l]);
+            dt = 1.0/(c.x[l+1]-c.x[l]);
+            ix = l;
+            
+            //
+            // Binary search in the [ y[0], ..., y[m-2] ] (y[m-1] is not included)
+            //
+            l = 0;
+            r = c.m-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.y[h])>=(double)(y) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            u = (y-c.y[l])/(c.y[l+1]-c.y[l]);
+            du = 1.0/(c.y[l+1]-c.y[l]);
+            iy = l;
+            
+            //
+            // Bilinear interpolation
+            //
+            if( c.stype==-1 )
+            {
+                y1 = c.f[c.n*iy+ix];
+                y2 = c.f[c.n*iy+(ix+1)];
+                y3 = c.f[c.n*(iy+1)+(ix+1)];
+                y4 = c.f[c.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( c.stype==-3 )
+            {
+                sfx = c.n*c.m;
+                sfy = 2*c.n*c.m;
+                sfxy = 3*c.n*c.m;
+                s1 = c.n*iy+ix;
+                s2 = c.n*iy+(ix+1);
+                s3 = c.n*(iy+1)+ix;
+                s4 = c.n*(iy+1)+(ix+1);
+                t2 = t*t;
+                t3 = t*t2;
+                u2 = u*u;
+                u3 = u*u2;
+                ht00 = 2*t3-3*t2+1;
+                ht10 = t3-2*t2+t;
+                ht01 = -(2*t3)+3*t2;
+                ht11 = t3-t2;
+                hu00 = 2*u3-3*u2+1;
+                hu10 = u3-2*u2+u;
+                hu01 = -(2*u3)+3*u2;
+                hu11 = u3-u2;
+                ht10 = ht10/dt;
+                ht11 = ht11/dt;
+                hu10 = hu10/du;
+                hu11 = hu11/du;
+                dht00 = 6*t2-6*t;
+                dht10 = 3*t2-4*t+1;
+                dht01 = -(6*t2)+6*t;
+                dht11 = 3*t2-2*t;
+                dhu00 = 6*u2-6*u;
+                dhu10 = 3*u2-4*u+1;
+                dhu01 = -(6*u2)+6*u;
+                dhu11 = 3*u2-2*u;
+                dht00 = dht00*dt;
+                dht01 = dht01*dt;
+                dhu00 = dhu00*du;
+                dhu01 = dhu01*du;
+                f = 0;
+                fx = 0;
+                fy = 0;
+                fxy = 0;
+                v0 = c.f[s1];
+                v1 = c.f[s2];
+                v2 = c.f[s3];
+                v3 = c.f[s4];
+                f = f+v0*ht00*hu00+v1*ht01*hu00+v2*ht00*hu01+v3*ht01*hu01;
+                fx = fx+v0*dht00*hu00+v1*dht01*hu00+v2*dht00*hu01+v3*dht01*hu01;
+                fy = fy+v0*ht00*dhu00+v1*ht01*dhu00+v2*ht00*dhu01+v3*ht01*dhu01;
+                fxy = fxy+v0*dht00*dhu00+v1*dht01*dhu00+v2*dht00*dhu01+v3*dht01*dhu01;
+                v0 = c.f[sfx+s1];
+                v1 = c.f[sfx+s2];
+                v2 = c.f[sfx+s3];
+                v3 = c.f[sfx+s4];
+                f = f+v0*ht10*hu00+v1*ht11*hu00+v2*ht10*hu01+v3*ht11*hu01;
+                fx = fx+v0*dht10*hu00+v1*dht11*hu00+v2*dht10*hu01+v3*dht11*hu01;
+                fy = fy+v0*ht10*dhu00+v1*ht11*dhu00+v2*ht10*dhu01+v3*ht11*dhu01;
+                fxy = fxy+v0*dht10*dhu00+v1*dht11*dhu00+v2*dht10*dhu01+v3*dht11*dhu01;
+                v0 = c.f[sfy+s1];
+                v1 = c.f[sfy+s2];
+                v2 = c.f[sfy+s3];
+                v3 = c.f[sfy+s4];
+                f = f+v0*ht00*hu10+v1*ht01*hu10+v2*ht00*hu11+v3*ht01*hu11;
+                fx = fx+v0*dht00*hu10+v1*dht01*hu10+v2*dht00*hu11+v3*dht01*hu11;
+                fy = fy+v0*ht00*dhu10+v1*ht01*dhu10+v2*ht00*dhu11+v3*ht01*dhu11;
+                fxy = fxy+v0*dht00*dhu10+v1*dht01*dhu10+v2*dht00*dhu11+v3*dht01*dhu11;
+                v0 = c.f[sfxy+s1];
+                v1 = c.f[sfxy+s2];
+                v2 = c.f[sfxy+s3];
+                v3 = c.f[sfxy+s4];
+                f = f+v0*ht10*hu10+v1*ht11*hu10+v2*ht10*hu11+v3*ht11*hu11;
+                fx = fx+v0*dht10*hu10+v1*dht11*hu10+v2*dht10*hu11+v3*dht11*hu11;
+                fy = fy+v0*ht10*dhu10+v1*ht11*dhu10+v2*ht10*dhu11+v3*ht11*dhu11;
+                fxy = fxy+v0*dht10*dhu10+v1*dht11*dhu10+v2*dht10*dhu11+v3*dht11*dhu11;
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates bilinear or bicubic vector-valued spline at the
+        given point (X,Y).
+
+        If you need just some specific component of vector-valued spline, you  can
+        use spline2dcalcvi() function.
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+            X, Y-   point
+            F   -   output buffer, possibly preallocated array. In case array size
+                    is large enough to store result, it is not reallocated.  Array
+                    which is too short will be reallocated
+
+        OUTPUT PARAMETERS:
+            F   -   array[D] (or larger) which stores function values
+
+          -- ALGLIB PROJECT --
+             Copyright 01.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dcalcvbuf(spline2dinterpolant c,
+            double x,
+            double y,
+            ref double[] f,
+            alglib.xparams _params)
+        {
+            int ix = 0;
+            int iy = 0;
+            int l = 0;
+            int r = 0;
+            int h = 0;
+            int i = 0;
+            double t = 0;
+            double dt = 0;
+            double u = 0;
+            double du = 0;
+            double y1 = 0;
+            double y2 = 0;
+            double y3 = 0;
+            double y4 = 0;
+            int s1 = 0;
+            int s2 = 0;
+            int s3 = 0;
+            int s4 = 0;
+            int sfx = 0;
+            int sfy = 0;
+            int sfxy = 0;
+            double t2 = 0;
+            double t3 = 0;
+            double u2 = 0;
+            double u3 = 0;
+            double ht00 = 0;
+            double ht01 = 0;
+            double ht10 = 0;
+            double ht11 = 0;
+            double hu00 = 0;
+            double hu01 = 0;
+            double hu10 = 0;
+            double hu11 = 0;
+
+            alglib.ap.assert(c.stype==-1 || c.stype==-3, "Spline2DCalcVBuf: incorrect C (incorrect parameter C.SType)");
+            alglib.ap.assert(math.isfinite(x) && math.isfinite(y), "Spline2DCalcVBuf: X or Y contains NaN or Infinite value");
+            
+            //
+            // Allocate place for output
+            //
+            apserv.rvectorsetlengthatleast(ref f, c.d, _params);
+            
+            //
+            // Determine evaluation interval
+            //
+            l = 0;
+            r = c.n-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.x[h])>=(double)(x) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            dt = 1.0/(c.x[l+1]-c.x[l]);
+            t = (x-c.x[l])*dt;
+            ix = l;
+            l = 0;
+            r = c.m-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.y[h])>=(double)(y) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            du = 1.0/(c.y[l+1]-c.y[l]);
+            u = (y-c.y[l])*du;
+            iy = l;
+            
+            //
+            // Bilinear interpolation
+            //
+            if( c.stype==-1 )
+            {
+                for(i=0; i<=c.d-1; i++)
+                {
+                    y1 = c.f[c.d*(c.n*iy+ix)+i];
+                    y2 = c.f[c.d*(c.n*iy+(ix+1))+i];
+                    y3 = c.f[c.d*(c.n*(iy+1)+(ix+1))+i];
+                    y4 = c.f[c.d*(c.n*(iy+1)+ix)+i];
+                    f[i] = (1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4;
+                }
+                return;
+            }
+            
+            //
+            // Bicubic interpolation:
+            // * calculate Hermite basis for dimensions X and Y (variables T and U),
+            //   here HTij means basis function whose I-th derivative has value 1 at T=J.
+            //   Same for HUij.
+            // * after initial calculation, apply scaling by DT/DU to the basis
+            // * calculate using stored table of second derivatives
+            //
+            alglib.ap.assert(c.stype==-3, "Spline2DCalc: integrity check failed");
+            sfx = c.n*c.m*c.d;
+            sfy = 2*c.n*c.m*c.d;
+            sfxy = 3*c.n*c.m*c.d;
+            s1 = (c.n*iy+ix)*c.d;
+            s2 = (c.n*iy+(ix+1))*c.d;
+            s3 = (c.n*(iy+1)+ix)*c.d;
+            s4 = (c.n*(iy+1)+(ix+1))*c.d;
+            t2 = t*t;
+            t3 = t*t2;
+            u2 = u*u;
+            u3 = u*u2;
+            ht00 = 2*t3-3*t2+1;
+            ht10 = t3-2*t2+t;
+            ht01 = -(2*t3)+3*t2;
+            ht11 = t3-t2;
+            hu00 = 2*u3-3*u2+1;
+            hu10 = u3-2*u2+u;
+            hu01 = -(2*u3)+3*u2;
+            hu11 = u3-u2;
+            ht10 = ht10/dt;
+            ht11 = ht11/dt;
+            hu10 = hu10/du;
+            hu11 = hu11/du;
+            for(i=0; i<=c.d-1; i++)
+            {
+                
+                //
+                // Calculate I-th component
+                //
+                f[i] = 0;
+                f[i] = f[i]+c.f[s1]*ht00*hu00+c.f[s2]*ht01*hu00+c.f[s3]*ht00*hu01+c.f[s4]*ht01*hu01;
+                f[i] = f[i]+c.f[sfx+s1]*ht10*hu00+c.f[sfx+s2]*ht11*hu00+c.f[sfx+s3]*ht10*hu01+c.f[sfx+s4]*ht11*hu01;
+                f[i] = f[i]+c.f[sfy+s1]*ht00*hu10+c.f[sfy+s2]*ht01*hu10+c.f[sfy+s3]*ht00*hu11+c.f[sfy+s4]*ht01*hu11;
+                f[i] = f[i]+c.f[sfxy+s1]*ht10*hu10+c.f[sfxy+s2]*ht11*hu10+c.f[sfxy+s3]*ht10*hu11+c.f[sfxy+s4]*ht11*hu11;
+                
+                //
+                // Advance source indexes
+                //
+                s1 = s1+1;
+                s2 = s2+1;
+                s3 = s3+1;
+                s4 = s4+1;
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates specific component of vector-valued bilinear or
+        bicubic spline at the given point (X,Y).
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+            X, Y-   point
+            I   -   component index, in [0,D). An exception is generated for out
+                    of range values.
+
+        RESULT:
+            value of I-th component
+
+          -- ALGLIB PROJECT --
+             Copyright 01.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static double spline2dcalcvi(spline2dinterpolant c,
+            double x,
+            double y,
+            int i,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int ix = 0;
+            int iy = 0;
+            int l = 0;
+            int r = 0;
+            int h = 0;
+            double t = 0;
+            double dt = 0;
+            double u = 0;
+            double du = 0;
+            double y1 = 0;
+            double y2 = 0;
+            double y3 = 0;
+            double y4 = 0;
+            int s1 = 0;
+            int s2 = 0;
+            int s3 = 0;
+            int s4 = 0;
+            int sfx = 0;
+            int sfy = 0;
+            int sfxy = 0;
+            double t2 = 0;
+            double t3 = 0;
+            double u2 = 0;
+            double u3 = 0;
+            double ht00 = 0;
+            double ht01 = 0;
+            double ht10 = 0;
+            double ht11 = 0;
+            double hu00 = 0;
+            double hu01 = 0;
+            double hu10 = 0;
+            double hu11 = 0;
+
+            alglib.ap.assert(c.stype==-1 || c.stype==-3, "Spline2DCalcVi: incorrect C (incorrect parameter C.SType)");
+            alglib.ap.assert(math.isfinite(x) && math.isfinite(y), "Spline2DCalcVi: X or Y contains NaN or Infinite value");
+            alglib.ap.assert(i>=0 && i<c.d, "Spline2DCalcVi: incorrect I (I<0 or I>=D)");
+            
+            //
+            // Determine evaluation interval
+            //
+            l = 0;
+            r = c.n-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.x[h])>=(double)(x) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            dt = 1.0/(c.x[l+1]-c.x[l]);
+            t = (x-c.x[l])*dt;
+            ix = l;
+            l = 0;
+            r = c.m-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.y[h])>=(double)(y) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            du = 1.0/(c.y[l+1]-c.y[l]);
+            u = (y-c.y[l])*du;
+            iy = l;
+            
+            //
+            // Bilinear interpolation
+            //
+            if( c.stype==-1 )
+            {
+                y1 = c.f[c.d*(c.n*iy+ix)+i];
+                y2 = c.f[c.d*(c.n*iy+(ix+1))+i];
+                y3 = c.f[c.d*(c.n*(iy+1)+(ix+1))+i];
+                y4 = c.f[c.d*(c.n*(iy+1)+ix)+i];
+                result = (1-t)*(1-u)*y1+t*(1-u)*y2+t*u*y3+(1-t)*u*y4;
+                return result;
+            }
+            
+            //
+            // Bicubic interpolation:
+            // * calculate Hermite basis for dimensions X and Y (variables T and U),
+            //   here HTij means basis function whose I-th derivative has value 1 at T=J.
+            //   Same for HUij.
+            // * after initial calculation, apply scaling by DT/DU to the basis
+            // * calculate using stored table of second derivatives
+            //
+            alglib.ap.assert(c.stype==-3, "Spline2DCalc: integrity check failed");
+            sfx = c.n*c.m*c.d;
+            sfy = 2*c.n*c.m*c.d;
+            sfxy = 3*c.n*c.m*c.d;
+            s1 = (c.n*iy+ix)*c.d;
+            s2 = (c.n*iy+(ix+1))*c.d;
+            s3 = (c.n*(iy+1)+ix)*c.d;
+            s4 = (c.n*(iy+1)+(ix+1))*c.d;
+            t2 = t*t;
+            t3 = t*t2;
+            u2 = u*u;
+            u3 = u*u2;
+            ht00 = 2*t3-3*t2+1;
+            ht10 = t3-2*t2+t;
+            ht01 = -(2*t3)+3*t2;
+            ht11 = t3-t2;
+            hu00 = 2*u3-3*u2+1;
+            hu10 = u3-2*u2+u;
+            hu01 = -(2*u3)+3*u2;
+            hu11 = u3-u2;
+            ht10 = ht10/dt;
+            ht11 = ht11/dt;
+            hu10 = hu10/du;
+            hu11 = hu11/du;
+            
+            //
+            // Advance source indexes to I-th position
+            //
+            s1 = s1+i;
+            s2 = s2+i;
+            s3 = s3+i;
+            s4 = s4+i;
+            
+            //
+            // Calculate I-th component
+            //
+            result = 0;
+            result = result+c.f[s1]*ht00*hu00+c.f[s2]*ht01*hu00+c.f[s3]*ht00*hu01+c.f[s4]*ht01*hu01;
+            result = result+c.f[sfx+s1]*ht10*hu00+c.f[sfx+s2]*ht11*hu00+c.f[sfx+s3]*ht10*hu01+c.f[sfx+s4]*ht11*hu01;
+            result = result+c.f[sfy+s1]*ht00*hu10+c.f[sfy+s2]*ht01*hu10+c.f[sfy+s3]*ht00*hu11+c.f[sfy+s4]*ht01*hu11;
+            result = result+c.f[sfxy+s1]*ht10*hu10+c.f[sfxy+s2]*ht11*hu10+c.f[sfxy+s3]*ht10*hu11+c.f[sfxy+s4]*ht11*hu11;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates bilinear or bicubic vector-valued spline at the
+        given point (X,Y).
+
+        INPUT PARAMETERS:
+            C   -   spline interpolant.
+            X, Y-   point
+
+        OUTPUT PARAMETERS:
+            F   -   array[D] which stores function values.  F is out-parameter and
+                    it  is  reallocated  after  call to this function. In case you
+                    want  to    reuse  previously  allocated  F,   you   may   use
+                    Spline2DCalcVBuf(),  which  reallocates  F only when it is too
+                    small.
+
+          -- ALGLIB PROJECT --
+             Copyright 16.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dcalcv(spline2dinterpolant c,
+            double x,
+            double y,
+            ref double[] f,
+            alglib.xparams _params)
+        {
+            f = new double[0];
+
+            alglib.ap.assert(c.stype==-1 || c.stype==-3, "Spline2DCalcV: incorrect C (incorrect parameter C.SType)");
+            alglib.ap.assert(math.isfinite(x) && math.isfinite(y), "Spline2DCalcV: either X=NaN/Infinite or Y=NaN/Infinite");
+            spline2dcalcvbuf(c, x, y, ref f, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates value of  specific  component  of  bilinear  or
+        bicubic vector-valued spline and its derivatives.
+
+        Input parameters:
+            C   -   spline interpolant.
+            X, Y-   point
+            I   -   component index, in [0,D)
+
+        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 spline2ddiffvi(spline2dinterpolant c,
+            double x,
+            double y,
+            int i,
+            ref double f,
+            ref double fx,
+            ref double fy,
+            ref double fxy,
+            alglib.xparams _params)
+        {
+            int d = 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 s1 = 0;
+            int s2 = 0;
+            int s3 = 0;
+            int s4 = 0;
+            int sfx = 0;
+            int sfy = 0;
+            int sfxy = 0;
+            double y1 = 0;
+            double y2 = 0;
+            double y3 = 0;
+            double y4 = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double v3 = 0;
+            double t2 = 0;
+            double t3 = 0;
+            double u2 = 0;
+            double u3 = 0;
+            double ht00 = 0;
+            double ht01 = 0;
+            double ht10 = 0;
+            double ht11 = 0;
+            double hu00 = 0;
+            double hu01 = 0;
+            double hu10 = 0;
+            double hu11 = 0;
+            double dht00 = 0;
+            double dht01 = 0;
+            double dht10 = 0;
+            double dht11 = 0;
+            double dhu00 = 0;
+            double dhu01 = 0;
+            double dhu10 = 0;
+            double dhu11 = 0;
+
+            f = 0;
+            fx = 0;
+            fy = 0;
+            fxy = 0;
+
+            alglib.ap.assert(c.stype==-1 || c.stype==-3, "Spline2DDiffVI: incorrect C (incorrect parameter C.SType)");
+            alglib.ap.assert(math.isfinite(x) && math.isfinite(y), "Spline2DDiffVI: X or Y contains NaN or Infinite value");
+            alglib.ap.assert(i>=0 && i<c.d, "Spline2DDiffVI: I<0 or I>=D");
+            
+            //
+            // Prepare F, dF/dX, dF/dY, d2F/dXdY
+            //
+            f = 0;
+            fx = 0;
+            fy = 0;
+            fxy = 0;
+            d = c.d;
+            
+            //
+            // Binary search in the [ x[0], ..., x[n-2] ] (x[n-1] is not included)
+            //
+            l = 0;
+            r = c.n-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.x[h])>=(double)(x) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            t = (x-c.x[l])/(c.x[l+1]-c.x[l]);
+            dt = 1.0/(c.x[l+1]-c.x[l]);
+            ix = l;
+            
+            //
+            // Binary search in the [ y[0], ..., y[m-2] ] (y[m-1] is not included)
+            //
+            l = 0;
+            r = c.m-1;
+            while( l!=r-1 )
+            {
+                h = (l+r)/2;
+                if( (double)(c.y[h])>=(double)(y) )
+                {
+                    r = h;
+                }
+                else
+                {
+                    l = h;
+                }
+            }
+            u = (y-c.y[l])/(c.y[l+1]-c.y[l]);
+            du = 1.0/(c.y[l+1]-c.y[l]);
+            iy = l;
+            
+            //
+            // Bilinear interpolation
+            //
+            if( c.stype==-1 )
+            {
+                y1 = c.f[d*(c.n*iy+ix)+i];
+                y2 = c.f[d*(c.n*iy+(ix+1))+i];
+                y3 = c.f[d*(c.n*(iy+1)+(ix+1))+i];
+                y4 = c.f[d*(c.n*(iy+1)+ix)+i];
+                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( c.stype==-3 )
+            {
+                sfx = c.n*c.m*d;
+                sfy = 2*c.n*c.m*d;
+                sfxy = 3*c.n*c.m*d;
+                s1 = d*(c.n*iy+ix)+i;
+                s2 = d*(c.n*iy+(ix+1))+i;
+                s3 = d*(c.n*(iy+1)+ix)+i;
+                s4 = d*(c.n*(iy+1)+(ix+1))+i;
+                t2 = t*t;
+                t3 = t*t2;
+                u2 = u*u;
+                u3 = u*u2;
+                ht00 = 2*t3-3*t2+1;
+                ht10 = t3-2*t2+t;
+                ht01 = -(2*t3)+3*t2;
+                ht11 = t3-t2;
+                hu00 = 2*u3-3*u2+1;
+                hu10 = u3-2*u2+u;
+                hu01 = -(2*u3)+3*u2;
+                hu11 = u3-u2;
+                ht10 = ht10/dt;
+                ht11 = ht11/dt;
+                hu10 = hu10/du;
+                hu11 = hu11/du;
+                dht00 = 6*t2-6*t;
+                dht10 = 3*t2-4*t+1;
+                dht01 = -(6*t2)+6*t;
+                dht11 = 3*t2-2*t;
+                dhu00 = 6*u2-6*u;
+                dhu10 = 3*u2-4*u+1;
+                dhu01 = -(6*u2)+6*u;
+                dhu11 = 3*u2-2*u;
+                dht00 = dht00*dt;
+                dht01 = dht01*dt;
+                dhu00 = dhu00*du;
+                dhu01 = dhu01*du;
+                f = 0;
+                fx = 0;
+                fy = 0;
+                fxy = 0;
+                v0 = c.f[s1];
+                v1 = c.f[s2];
+                v2 = c.f[s3];
+                v3 = c.f[s4];
+                f = f+v0*ht00*hu00+v1*ht01*hu00+v2*ht00*hu01+v3*ht01*hu01;
+                fx = fx+v0*dht00*hu00+v1*dht01*hu00+v2*dht00*hu01+v3*dht01*hu01;
+                fy = fy+v0*ht00*dhu00+v1*ht01*dhu00+v2*ht00*dhu01+v3*ht01*dhu01;
+                fxy = fxy+v0*dht00*dhu00+v1*dht01*dhu00+v2*dht00*dhu01+v3*dht01*dhu01;
+                v0 = c.f[sfx+s1];
+                v1 = c.f[sfx+s2];
+                v2 = c.f[sfx+s3];
+                v3 = c.f[sfx+s4];
+                f = f+v0*ht10*hu00+v1*ht11*hu00+v2*ht10*hu01+v3*ht11*hu01;
+                fx = fx+v0*dht10*hu00+v1*dht11*hu00+v2*dht10*hu01+v3*dht11*hu01;
+                fy = fy+v0*ht10*dhu00+v1*ht11*dhu00+v2*ht10*dhu01+v3*ht11*dhu01;
+                fxy = fxy+v0*dht10*dhu00+v1*dht11*dhu00+v2*dht10*dhu01+v3*dht11*dhu01;
+                v0 = c.f[sfy+s1];
+                v1 = c.f[sfy+s2];
+                v2 = c.f[sfy+s3];
+                v3 = c.f[sfy+s4];
+                f = f+v0*ht00*hu10+v1*ht01*hu10+v2*ht00*hu11+v3*ht01*hu11;
+                fx = fx+v0*dht00*hu10+v1*dht01*hu10+v2*dht00*hu11+v3*dht01*hu11;
+                fy = fy+v0*ht00*dhu10+v1*ht01*dhu10+v2*ht00*dhu11+v3*ht01*dhu11;
+                fxy = fxy+v0*dht00*dhu10+v1*dht01*dhu10+v2*dht00*dhu11+v3*dht01*dhu11;
+                v0 = c.f[sfxy+s1];
+                v1 = c.f[sfxy+s2];
+                v2 = c.f[sfxy+s3];
+                v3 = c.f[sfxy+s4];
+                f = f+v0*ht10*hu10+v1*ht11*hu10+v2*ht10*hu11+v3*ht11*hu11;
+                fx = fx+v0*dht10*hu10+v1*dht11*hu10+v2*dht10*hu11+v3*dht11*hu11;
+                fy = fy+v0*ht10*dhu10+v1*ht11*dhu10+v2*ht10*dhu11+v3*ht11*dhu11;
+                fxy = fxy+v0*dht10*dhu10+v1*dht11*dhu10+v2*dht10*dhu11+v3*dht11*dhu11;
+                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,
+            alglib.xparams _params)
+        {
+            double[] x = new double[0];
+            double[] y = new double[0];
+            double[] f = new double[0];
+            double[] v = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+
+            alglib.ap.assert(c.stype==-3 || c.stype==-1, "Spline2DLinTransXY: incorrect C (incorrect parameter C.SType)");
+            alglib.ap.assert(math.isfinite(ax), "Spline2DLinTransXY: AX is infinite or NaN");
+            alglib.ap.assert(math.isfinite(bx), "Spline2DLinTransXY: BX is infinite or NaN");
+            alglib.ap.assert(math.isfinite(ay), "Spline2DLinTransXY: AY is infinite or NaN");
+            alglib.ap.assert(math.isfinite(by), "Spline2DLinTransXY: BY is infinite or NaN");
+            x = new double[c.n];
+            y = new double[c.m];
+            f = new double[c.m*c.n*c.d];
+            for(j=0; j<=c.n-1; j++)
+            {
+                x[j] = c.x[j];
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                y[i] = c.y[i];
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                for(j=0; j<=c.n-1; j++)
+                {
+                    for(k=0; k<=c.d-1; k++)
+                    {
+                        f[c.d*(i*c.n+j)+k] = c.f[c.d*(i*c.n+j)+k];
+                    }
+                }
+            }
+            
+            //
+            // Handle different combinations of AX/AY
+            //
+            if( (double)(ax)==(double)(0) && (double)(ay)!=(double)(0) )
+            {
+                for(i=0; i<=c.m-1; i++)
+                {
+                    spline2dcalcvbuf(c, bx, y[i], ref v, _params);
+                    y[i] = (y[i]-by)/ay;
+                    for(j=0; j<=c.n-1; j++)
+                    {
+                        for(k=0; k<=c.d-1; k++)
+                        {
+                            f[c.d*(i*c.n+j)+k] = v[k];
+                        }
+                    }
+                }
+            }
+            if( (double)(ax)!=(double)(0) && (double)(ay)==(double)(0) )
+            {
+                for(j=0; j<=c.n-1; j++)
+                {
+                    spline2dcalcvbuf(c, x[j], by, ref v, _params);
+                    x[j] = (x[j]-bx)/ax;
+                    for(i=0; i<=c.m-1; i++)
+                    {
+                        for(k=0; k<=c.d-1; k++)
+                        {
+                            f[c.d*(i*c.n+j)+k] = v[k];
+                        }
+                    }
+                }
+            }
+            if( (double)(ax)!=(double)(0) && (double)(ay)!=(double)(0) )
+            {
+                for(j=0; j<=c.n-1; j++)
+                {
+                    x[j] = (x[j]-bx)/ax;
+                }
+                for(i=0; i<=c.m-1; i++)
+                {
+                    y[i] = (y[i]-by)/ay;
+                }
+            }
+            if( (double)(ax)==(double)(0) && (double)(ay)==(double)(0) )
+            {
+                spline2dcalcvbuf(c, bx, by, ref v, _params);
+                for(i=0; i<=c.m-1; i++)
+                {
+                    for(j=0; j<=c.n-1; j++)
+                    {
+                        for(k=0; k<=c.d-1; k++)
+                        {
+                            f[c.d*(i*c.n+j)+k] = v[k];
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Rebuild spline
+            //
+            if( c.stype==-3 )
+            {
+                spline2dbuildbicubicv(x, c.n, y, c.m, f, c.d, c, _params);
+            }
+            if( c.stype==-1 )
+            {
+                spline2dbuildbilinearv(x, c.n, y, c.m, f, c.d, c, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            double[] x = new double[0];
+            double[] y = new double[0];
+            double[] f = new double[0];
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(c.stype==-3 || c.stype==-1, "Spline2DLinTransF: incorrect C (incorrect parameter C.SType)");
+            x = new double[c.n];
+            y = new double[c.m];
+            f = new double[c.m*c.n*c.d];
+            for(j=0; j<=c.n-1; j++)
+            {
+                x[j] = c.x[j];
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                y[i] = c.y[i];
+            }
+            for(i=0; i<=c.m*c.n*c.d-1; i++)
+            {
+                f[i] = a*c.f[i]+b;
+            }
+            if( c.stype==-3 )
+            {
+                spline2dbuildbicubicv(x, c.n, y, c.m, f, c.d, c, _params);
+            }
+            if( c.stype==-1 )
+            {
+                spline2dbuildbilinearv(x, c.n, y, c.m, f, c.d, c, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int tblsize = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(c.stype==-1 || c.stype==-3, "Spline2DCopy: incorrect C (incorrect parameter C.SType)");
+            cc.n = c.n;
+            cc.m = c.m;
+            cc.d = c.d;
+            cc.stype = c.stype;
+            tblsize = -1;
+            if( c.stype==-3 )
+            {
+                tblsize = 4*c.n*c.m*c.d;
+            }
+            if( c.stype==-1 )
+            {
+                tblsize = c.n*c.m*c.d;
+            }
+            alglib.ap.assert(tblsize>0, "Spline2DCopy: internal error");
+            cc.x = new double[cc.n];
+            cc.y = new double[cc.m];
+            cc.f = new double[tblsize];
+            for(i_=0; i_<=cc.n-1;i_++)
+            {
+                cc.x[i_] = c.x[i_];
+            }
+            for(i_=0; i_<=cc.m-1;i_++)
+            {
+                cc.y[i_] = c.y[i_];
+            }
+            for(i_=0; i_<=tblsize-1;i_++)
+            {
+                cc.f[i_] = c.f[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,
+            alglib.xparams _params)
+        {
+            double[,] buf = new double[0,0];
+            double[] x = new double[0];
+            double[] y = new double[0];
+            spline1d.spline1dinterpolant c = new spline1d.spline1dinterpolant();
+            int mw = 0;
+            int mh = 0;
+            int i = 0;
+            int j = 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, newwidth];
+            buf = new double[oldheight, newwidth];
+            x = new double[Math.Max(mw, mh)];
+            y = new double[Math.Max(mw, mh)];
+            
+            //
+            // 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, _params);
+                for(j=0; j<=newwidth-1; j++)
+                {
+                    buf[i,j] = spline1d.spline1dcalc(c, (double)j/(double)(newwidth-1), _params);
+                }
+            }
+            
+            //
+            // 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, _params);
+                for(i=0; i<=newheight-1; i++)
+                {
+                    b[i,j] = spline1d.spline1dcalc(c, (double)i/(double)(newheight-1), _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int l = 0;
+            int c = 0;
+            double t = 0;
+            double u = 0;
+            int i = 0;
+            int j = 0;
+
+            b = new double[0,0];
+
+            alglib.ap.assert(oldwidth>1 && oldheight>1, "Spline2DResampleBilinear: width/height less than 1");
+            alglib.ap.assert(newwidth>1 && newheight>1, "Spline2DResampleBilinear: width/height less than 1");
+            b = new double[newheight, newwidth];
+            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];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine builds bilinear vector-valued spline.
+
+        Input parameters:
+            X   -   spline abscissas, array[0..N-1]
+            Y   -   spline ordinates, array[0..M-1]
+            F   -   function values, array[0..M*N*D-1]:
+                    * first D elements store D values at (X[0],Y[0])
+                    * next D elements store D values at (X[1],Y[0])
+                    * general form - D function values at (X[i],Y[j]) are stored
+                      at F[D*(J*N+I)...D*(J*N+I)+D-1].
+            M,N -   grid size, M>=2, N>=2
+            D   -   vector dimension, D>=1
+
+        Output parameters:
+            C   -   spline interpolant
+
+          -- ALGLIB PROJECT --
+             Copyright 16.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildbilinearv(double[] x,
+            int n,
+            double[] y,
+            int m,
+            double[] f,
+            int d,
+            spline2dinterpolant c,
+            alglib.xparams _params)
+        {
+            double t = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int i0 = 0;
+
+            alglib.ap.assert(n>=2, "Spline2DBuildBilinearV: N is less then 2");
+            alglib.ap.assert(m>=2, "Spline2DBuildBilinearV: M is less then 2");
+            alglib.ap.assert(d>=1, "Spline2DBuildBilinearV: invalid argument D (D<1)");
+            alglib.ap.assert(alglib.ap.len(x)>=n && alglib.ap.len(y)>=m, "Spline2DBuildBilinearV: length of X or Y is too short (Length(X/Y)<N/M)");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params) && apserv.isfinitevector(y, m, _params), "Spline2DBuildBilinearV: X or Y contains NaN or Infinite value");
+            k = n*m*d;
+            alglib.ap.assert(alglib.ap.len(f)>=k, "Spline2DBuildBilinearV: length of F is too short (Length(F)<N*M*D)");
+            alglib.ap.assert(apserv.isfinitevector(f, k, _params), "Spline2DBuildBilinearV: F contains NaN or Infinite value");
+            
+            //
+            // Fill interpolant
+            //
+            c.n = n;
+            c.m = m;
+            c.d = d;
+            c.stype = -1;
+            c.x = new double[c.n];
+            c.y = new double[c.m];
+            c.f = new double[k];
+            for(i=0; i<=c.n-1; i++)
+            {
+                c.x[i] = x[i];
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                c.y[i] = y[i];
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                c.f[i] = f[i];
+            }
+            
+            //
+            // Sort points
+            //
+            for(j=0; j<=c.n-1; j++)
+            {
+                k = j;
+                for(i=j+1; i<=c.n-1; i++)
+                {
+                    if( (double)(c.x[i])<(double)(c.x[k]) )
+                    {
+                        k = i;
+                    }
+                }
+                if( k!=j )
+                {
+                    for(i=0; i<=c.m-1; i++)
+                    {
+                        for(i0=0; i0<=c.d-1; i0++)
+                        {
+                            t = c.f[c.d*(i*c.n+j)+i0];
+                            c.f[c.d*(i*c.n+j)+i0] = c.f[c.d*(i*c.n+k)+i0];
+                            c.f[c.d*(i*c.n+k)+i0] = t;
+                        }
+                    }
+                    t = c.x[j];
+                    c.x[j] = c.x[k];
+                    c.x[k] = t;
+                }
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                k = i;
+                for(j=i+1; j<=c.m-1; j++)
+                {
+                    if( (double)(c.y[j])<(double)(c.y[k]) )
+                    {
+                        k = j;
+                    }
+                }
+                if( k!=i )
+                {
+                    for(j=0; j<=c.n-1; j++)
+                    {
+                        for(i0=0; i0<=c.d-1; i0++)
+                        {
+                            t = c.f[c.d*(i*c.n+j)+i0];
+                            c.f[c.d*(i*c.n+j)+i0] = c.f[c.d*(k*c.n+j)+i0];
+                            c.f[c.d*(k*c.n+j)+i0] = t;
+                        }
+                    }
+                    t = c.y[i];
+                    c.y[i] = c.y[k];
+                    c.y[k] = t;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine builds bicubic vector-valued spline.
+
+        Input parameters:
+            X   -   spline abscissas, array[0..N-1]
+            Y   -   spline ordinates, array[0..M-1]
+            F   -   function values, array[0..M*N*D-1]:
+                    * first D elements store D values at (X[0],Y[0])
+                    * next D elements store D values at (X[1],Y[0])
+                    * general form - D function values at (X[i],Y[j]) are stored
+                      at F[D*(J*N+I)...D*(J*N+I)+D-1].
+            M,N -   grid size, M>=2, N>=2
+            D   -   vector dimension, D>=1
+
+        Output parameters:
+            C   -   spline interpolant
+
+          -- ALGLIB PROJECT --
+             Copyright 16.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildbicubicv(double[] x,
+            int n,
+            double[] y,
+            int m,
+            double[] f,
+            int d,
+            spline2dinterpolant c,
+            alglib.xparams _params)
+        {
+            double[,] tf = new double[0,0];
+            double[,] dx = new double[0,0];
+            double[,] dy = new double[0,0];
+            double[,] dxy = new double[0,0];
+            double t = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int di = 0;
+
+            f = (double[])f.Clone();
+
+            alglib.ap.assert(n>=2, "Spline2DBuildBicubicV: N is less than 2");
+            alglib.ap.assert(m>=2, "Spline2DBuildBicubicV: M is less than 2");
+            alglib.ap.assert(d>=1, "Spline2DBuildBicubicV: invalid argument D (D<1)");
+            alglib.ap.assert(alglib.ap.len(x)>=n && alglib.ap.len(y)>=m, "Spline2DBuildBicubicV: length of X or Y is too short (Length(X/Y)<N/M)");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params) && apserv.isfinitevector(y, m, _params), "Spline2DBuildBicubicV: X or Y contains NaN or Infinite value");
+            k = n*m*d;
+            alglib.ap.assert(alglib.ap.len(f)>=k, "Spline2DBuildBicubicV: length of F is too short (Length(F)<N*M*D)");
+            alglib.ap.assert(apserv.isfinitevector(f, k, _params), "Spline2DBuildBicubicV: F contains NaN or Infinite value");
+            
+            //
+            // Fill interpolant:
+            //  F[0]...F[N*M*D-1]:
+            //      f(i,j) table. f(0,0), f(0, 1), f(0,2) and so on...
+            //  F[N*M*D]...F[2*N*M*D-1]:
+            //      df(i,j)/dx table.
+            //  F[2*N*M*D]...F[3*N*M*D-1]:
+            //      df(i,j)/dy table.
+            //  F[3*N*M*D]...F[4*N*M*D-1]:
+            //      d2f(i,j)/dxdy table.
+            //
+            c.d = d;
+            c.n = n;
+            c.m = m;
+            c.stype = -3;
+            k = 4*k;
+            c.x = new double[c.n];
+            c.y = new double[c.m];
+            c.f = new double[k];
+            tf = new double[c.m, c.n];
+            for(i=0; i<=c.n-1; i++)
+            {
+                c.x[i] = x[i];
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                c.y[i] = y[i];
+            }
+            
+            //
+            // Sort points
+            //
+            for(j=0; j<=c.n-1; j++)
+            {
+                k = j;
+                for(i=j+1; i<=c.n-1; i++)
+                {
+                    if( (double)(c.x[i])<(double)(c.x[k]) )
+                    {
+                        k = i;
+                    }
+                }
+                if( k!=j )
+                {
+                    for(i=0; i<=c.m-1; i++)
+                    {
+                        for(di=0; di<=c.d-1; di++)
+                        {
+                            t = f[c.d*(i*c.n+j)+di];
+                            f[c.d*(i*c.n+j)+di] = f[c.d*(i*c.n+k)+di];
+                            f[c.d*(i*c.n+k)+di] = t;
+                        }
+                    }
+                    t = c.x[j];
+                    c.x[j] = c.x[k];
+                    c.x[k] = t;
+                }
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                k = i;
+                for(j=i+1; j<=c.m-1; j++)
+                {
+                    if( (double)(c.y[j])<(double)(c.y[k]) )
+                    {
+                        k = j;
+                    }
+                }
+                if( k!=i )
+                {
+                    for(j=0; j<=c.n-1; j++)
+                    {
+                        for(di=0; di<=c.d-1; di++)
+                        {
+                            t = f[c.d*(i*c.n+j)+di];
+                            f[c.d*(i*c.n+j)+di] = f[c.d*(k*c.n+j)+di];
+                            f[c.d*(k*c.n+j)+di] = t;
+                        }
+                    }
+                    t = c.y[i];
+                    c.y[i] = c.y[k];
+                    c.y[k] = t;
+                }
+            }
+            for(di=0; di<=c.d-1; di++)
+            {
+                for(i=0; i<=c.m-1; i++)
+                {
+                    for(j=0; j<=c.n-1; j++)
+                    {
+                        tf[i,j] = f[c.d*(i*c.n+j)+di];
+                    }
+                }
+                bicubiccalcderivatives(tf, c.x, c.y, c.m, c.n, ref dx, ref dy, ref dxy, _params);
+                for(i=0; i<=c.m-1; i++)
+                {
+                    for(j=0; j<=c.n-1; j++)
+                    {
+                        k = c.d*(i*c.n+j)+di;
+                        c.f[k] = tf[i,j];
+                        c.f[c.n*c.m*c.d+k] = dx[i,j];
+                        c.f[2*c.n*c.m*c.d+k] = dy[i,j];
+                        c.f[3*c.n*c.m*c.d+k] = dxy[i,j];
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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)
+            D   -   number of components
+            Tbl -   coefficients table, unpacked format,
+                    D - components: [0..(N-1)*(M-1)*D-1, 0..19].
+                    For T=0..D-1 (component index), I = 0...N-2 (x index),
+                    J=0..M-2 (y index):
+                        K :=  T + I*D + J*D*(N-1)
+                        
+                        K-th row stores decomposition for T-th component of the
+                        vector-valued function
+                        
+                        Tbl[K,0] = X[i]
+                        Tbl[K,1] = X[i+1]
+                        Tbl[K,2] = Y[j]
+                        Tbl[K,3] = Y[j+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]*(t^i)*(u^j), i=0..3, j=0..3)
+                        t = x-x[j]
+                        u = y-y[i]
+
+          -- ALGLIB PROJECT --
+             Copyright 16.04.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dunpackv(spline2dinterpolant c,
+            ref int m,
+            ref int n,
+            ref int d,
+            ref double[,] tbl,
+            alglib.xparams _params)
+        {
+            int k = 0;
+            int p = 0;
+            int ci = 0;
+            int cj = 0;
+            int s1 = 0;
+            int s2 = 0;
+            int s3 = 0;
+            int s4 = 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;
+            int i = 0;
+            int j = 0;
+            int k0 = 0;
+
+            m = 0;
+            n = 0;
+            d = 0;
+            tbl = new double[0,0];
+
+            alglib.ap.assert(c.stype==-3 || c.stype==-1, "Spline2DUnpackV: incorrect C (incorrect parameter C.SType)");
+            n = c.n;
+            m = c.m;
+            d = c.d;
+            tbl = new double[(n-1)*(m-1)*d, 20];
+            sfx = n*m*d;
+            sfy = 2*n*m*d;
+            sfxy = 3*n*m*d;
+            for(i=0; i<=m-2; i++)
+            {
+                for(j=0; j<=n-2; j++)
+                {
+                    for(k=0; k<=d-1; k++)
+                    {
+                        p = d*(i*(n-1)+j)+k;
+                        tbl[p,0] = c.x[j];
+                        tbl[p,1] = c.x[j+1];
+                        tbl[p,2] = c.y[i];
+                        tbl[p,3] = c.y[i+1];
+                        dt = 1/(tbl[p,1]-tbl[p,0]);
+                        du = 1/(tbl[p,3]-tbl[p,2]);
+                        
+                        //
+                        // Bilinear interpolation
+                        //
+                        if( c.stype==-1 )
+                        {
+                            for(k0=4; k0<=19; k0++)
+                            {
+                                tbl[p,k0] = 0;
+                            }
+                            y1 = c.f[d*(n*i+j)+k];
+                            y2 = c.f[d*(n*i+(j+1))+k];
+                            y3 = c.f[d*(n*(i+1)+(j+1))+k];
+                            y4 = c.f[d*(n*(i+1)+j)+k];
+                            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( c.stype==-3 )
+                        {
+                            s1 = d*(n*i+j)+k;
+                            s2 = d*(n*i+(j+1))+k;
+                            s3 = d*(n*(i+1)+(j+1))+k;
+                            s4 = d*(n*(i+1)+j)+k;
+                            tbl[p,4+0*4+0] = c.f[s1];
+                            tbl[p,4+0*4+1] = c.f[sfy+s1]/du;
+                            tbl[p,4+0*4+2] = -(3*c.f[s1])+3*c.f[s4]-2*c.f[sfy+s1]/du-c.f[sfy+s4]/du;
+                            tbl[p,4+0*4+3] = 2*c.f[s1]-2*c.f[s4]+c.f[sfy+s1]/du+c.f[sfy+s4]/du;
+                            tbl[p,4+1*4+0] = c.f[sfx+s1]/dt;
+                            tbl[p,4+1*4+1] = c.f[sfxy+s1]/(dt*du);
+                            tbl[p,4+1*4+2] = -(3*c.f[sfx+s1]/dt)+3*c.f[sfx+s4]/dt-2*c.f[sfxy+s1]/(dt*du)-c.f[sfxy+s4]/(dt*du);
+                            tbl[p,4+1*4+3] = 2*c.f[sfx+s1]/dt-2*c.f[sfx+s4]/dt+c.f[sfxy+s1]/(dt*du)+c.f[sfxy+s4]/(dt*du);
+                            tbl[p,4+2*4+0] = -(3*c.f[s1])+3*c.f[s2]-2*c.f[sfx+s1]/dt-c.f[sfx+s2]/dt;
+                            tbl[p,4+2*4+1] = -(3*c.f[sfy+s1]/du)+3*c.f[sfy+s2]/du-2*c.f[sfxy+s1]/(dt*du)-c.f[sfxy+s2]/(dt*du);
+                            tbl[p,4+2*4+2] = 9*c.f[s1]-9*c.f[s2]+9*c.f[s3]-9*c.f[s4]+6*c.f[sfx+s1]/dt+3*c.f[sfx+s2]/dt-3*c.f[sfx+s3]/dt-6*c.f[sfx+s4]/dt+6*c.f[sfy+s1]/du-6*c.f[sfy+s2]/du-3*c.f[sfy+s3]/du+3*c.f[sfy+s4]/du+4*c.f[sfxy+s1]/(dt*du)+2*c.f[sfxy+s2]/(dt*du)+c.f[sfxy+s3]/(dt*du)+2*c.f[sfxy+s4]/(dt*du);
+                            tbl[p,4+2*4+3] = -(6*c.f[s1])+6*c.f[s2]-6*c.f[s3]+6*c.f[s4]-4*c.f[sfx+s1]/dt-2*c.f[sfx+s2]/dt+2*c.f[sfx+s3]/dt+4*c.f[sfx+s4]/dt-3*c.f[sfy+s1]/du+3*c.f[sfy+s2]/du+3*c.f[sfy+s3]/du-3*c.f[sfy+s4]/du-2*c.f[sfxy+s1]/(dt*du)-c.f[sfxy+s2]/(dt*du)-c.f[sfxy+s3]/(dt*du)-2*c.f[sfxy+s4]/(dt*du);
+                            tbl[p,4+3*4+0] = 2*c.f[s1]-2*c.f[s2]+c.f[sfx+s1]/dt+c.f[sfx+s2]/dt;
+                            tbl[p,4+3*4+1] = 2*c.f[sfy+s1]/du-2*c.f[sfy+s2]/du+c.f[sfxy+s1]/(dt*du)+c.f[sfxy+s2]/(dt*du);
+                            tbl[p,4+3*4+2] = -(6*c.f[s1])+6*c.f[s2]-6*c.f[s3]+6*c.f[s4]-3*c.f[sfx+s1]/dt-3*c.f[sfx+s2]/dt+3*c.f[sfx+s3]/dt+3*c.f[sfx+s4]/dt-4*c.f[sfy+s1]/du+4*c.f[sfy+s2]/du+2*c.f[sfy+s3]/du-2*c.f[sfy+s4]/du-2*c.f[sfxy+s1]/(dt*du)-2*c.f[sfxy+s2]/(dt*du)-c.f[sfxy+s3]/(dt*du)-c.f[sfxy+s4]/(dt*du);
+                            tbl[p,4+3*4+3] = 4*c.f[s1]-4*c.f[s2]+4*c.f[s3]-4*c.f[s4]+2*c.f[sfx+s1]/dt+2*c.f[sfx+s2]/dt-2*c.f[sfx+s3]/dt-2*c.f[sfx+s4]/dt+2*c.f[sfy+s1]/du-2*c.f[sfy+s2]/du-2*c.f[sfy+s3]/du+2*c.f[sfy+s4]/du+c.f[sfxy+s1]/(dt*du)+c.f[sfxy+s2]/(dt*du)+c.f[sfxy+s3]/(dt*du)+c.f[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 was deprecated in ALGLIB 3.6.0
+
+        We recommend you to switch  to  Spline2DBuildBilinearV(),  which  is  more
+        flexible and accepts its arguments in more convenient order.
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            double t = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+
+            alglib.ap.assert(n>=2, "Spline2DBuildBilinear: N<2");
+            alglib.ap.assert(m>=2, "Spline2DBuildBilinear: M<2");
+            alglib.ap.assert(alglib.ap.len(x)>=n && alglib.ap.len(y)>=m, "Spline2DBuildBilinear: length of X or Y is too short (Length(X/Y)<N/M)");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params) && apserv.isfinitevector(y, m, _params), "Spline2DBuildBilinear: X or Y contains NaN or Infinite value");
+            alglib.ap.assert(alglib.ap.rows(f)>=m && alglib.ap.cols(f)>=n, "Spline2DBuildBilinear: size of F is too small (rows(F)<M or cols(F)<N)");
+            alglib.ap.assert(apserv.apservisfinitematrix(f, m, n, _params), "Spline2DBuildBilinear: F contains NaN or Infinite value");
+            
+            //
+            // Fill interpolant
+            //
+            c.n = n;
+            c.m = m;
+            c.d = 1;
+            c.stype = -1;
+            c.x = new double[c.n];
+            c.y = new double[c.m];
+            c.f = new double[c.n*c.m];
+            for(i=0; i<=c.n-1; i++)
+            {
+                c.x[i] = x[i];
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                c.y[i] = y[i];
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                for(j=0; j<=c.n-1; j++)
+                {
+                    c.f[i*c.n+j] = f[i,j];
+                }
+            }
+            
+            //
+            // Sort points
+            //
+            for(j=0; j<=c.n-1; j++)
+            {
+                k = j;
+                for(i=j+1; i<=c.n-1; i++)
+                {
+                    if( (double)(c.x[i])<(double)(c.x[k]) )
+                    {
+                        k = i;
+                    }
+                }
+                if( k!=j )
+                {
+                    for(i=0; i<=c.m-1; i++)
+                    {
+                        t = c.f[i*c.n+j];
+                        c.f[i*c.n+j] = c.f[i*c.n+k];
+                        c.f[i*c.n+k] = t;
+                    }
+                    t = c.x[j];
+                    c.x[j] = c.x[k];
+                    c.x[k] = t;
+                }
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                k = i;
+                for(j=i+1; j<=c.m-1; j++)
+                {
+                    if( (double)(c.y[j])<(double)(c.y[k]) )
+                    {
+                        k = j;
+                    }
+                }
+                if( k!=i )
+                {
+                    for(j=0; j<=c.n-1; j++)
+                    {
+                        t = c.f[i*c.n+j];
+                        c.f[i*c.n+j] = c.f[k*c.n+j];
+                        c.f[k*c.n+j] = t;
+                    }
+                    t = c.y[i];
+                    c.y[i] = c.y[k];
+                    c.y[k] = t;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine was deprecated in ALGLIB 3.6.0
+
+        We recommend you to switch  to  Spline2DBuildBicubicV(),  which  is  more
+        flexible and accepts its arguments in more convenient order.
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            int sfx = 0;
+            int sfy = 0;
+            int sfxy = 0;
+            double[,] dx = new double[0,0];
+            double[,] dy = new double[0,0];
+            double[,] dxy = new double[0,0];
+            double t = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+
+            f = (double[,])f.Clone();
+
+            alglib.ap.assert(n>=2, "Spline2DBuildBicubicSpline: N<2");
+            alglib.ap.assert(m>=2, "Spline2DBuildBicubicSpline: M<2");
+            alglib.ap.assert(alglib.ap.len(x)>=n && alglib.ap.len(y)>=m, "Spline2DBuildBicubic: length of X or Y is too short (Length(X/Y)<N/M)");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params) && apserv.isfinitevector(y, m, _params), "Spline2DBuildBicubic: X or Y contains NaN or Infinite value");
+            alglib.ap.assert(alglib.ap.rows(f)>=m && alglib.ap.cols(f)>=n, "Spline2DBuildBicubic: size of F is too small (rows(F)<M or cols(F)<N)");
+            alglib.ap.assert(apserv.apservisfinitematrix(f, m, n, _params), "Spline2DBuildBicubic: F contains NaN or Infinite value");
+            
+            //
+            // Fill interpolant:
+            //  F[0]...F[N*M-1]:
+            //      f(i,j) table. f(0,0), f(0, 1), f(0,2) and so on...
+            //  F[N*M]...F[2*N*M-1]:
+            //      df(i,j)/dx table.
+            //  F[2*N*M]...F[3*N*M-1]:
+            //      df(i,j)/dy table.
+            //  F[3*N*M]...F[4*N*M-1]:
+            //      d2f(i,j)/dxdy table.
+            //
+            c.d = 1;
+            c.n = n;
+            c.m = m;
+            c.stype = -3;
+            sfx = c.n*c.m;
+            sfy = 2*c.n*c.m;
+            sfxy = 3*c.n*c.m;
+            c.x = new double[c.n];
+            c.y = new double[c.m];
+            c.f = new double[4*c.n*c.m];
+            for(i=0; i<=c.n-1; i++)
+            {
+                c.x[i] = x[i];
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                c.y[i] = y[i];
+            }
+            
+            //
+            // Sort points
+            //
+            for(j=0; j<=c.n-1; j++)
+            {
+                k = j;
+                for(i=j+1; i<=c.n-1; i++)
+                {
+                    if( (double)(c.x[i])<(double)(c.x[k]) )
+                    {
+                        k = i;
+                    }
+                }
+                if( k!=j )
+                {
+                    for(i=0; i<=c.m-1; i++)
+                    {
+                        t = f[i,j];
+                        f[i,j] = f[i,k];
+                        f[i,k] = t;
+                    }
+                    t = c.x[j];
+                    c.x[j] = c.x[k];
+                    c.x[k] = t;
+                }
+            }
+            for(i=0; i<=c.m-1; i++)
+            {
+                k = i;
+                for(j=i+1; j<=c.m-1; j++)
+                {
+                    if( (double)(c.y[j])<(double)(c.y[k]) )
+                    {
+                        k = j;
+                    }
+                }
+                if( k!=i )
+                {
+                    for(j=0; j<=c.n-1; j++)
+                    {
+                        t = f[i,j];
+                        f[i,j] = f[k,j];
+                        f[k,j] = t;
+                    }
+                    t = c.y[i];
+                    c.y[i] = c.y[k];
+                    c.y[k] = t;
+                }
+            }
+            bicubiccalcderivatives(f, c.x, c.y, c.m, c.n, ref dx, ref dy, ref dxy, _params);
+            for(i=0; i<=c.m-1; i++)
+            {
+                for(j=0; j<=c.n-1; j++)
+                {
+                    k = i*c.n+j;
+                    c.f[k] = f[i,j];
+                    c.f[sfx+k] = dx[i,j];
+                    c.f[sfy+k] = dy[i,j];
+                    c.f[sfxy+k] = dxy[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine was deprecated in ALGLIB 3.6.0
+
+        We recommend you to switch  to  Spline2DUnpackV(),  which is more flexible
+        and accepts its arguments in more convenient order.
+
+          -- ALGLIB PROJECT --
+             Copyright 29.06.2007 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dunpack(spline2dinterpolant c,
+            ref int m,
+            ref int n,
+            ref double[,] tbl,
+            alglib.xparams _params)
+        {
+            int k = 0;
+            int p = 0;
+            int ci = 0;
+            int cj = 0;
+            int s1 = 0;
+            int s2 = 0;
+            int s3 = 0;
+            int s4 = 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;
+            int i = 0;
+            int j = 0;
+
+            m = 0;
+            n = 0;
+            tbl = new double[0,0];
+
+            alglib.ap.assert(c.stype==-3 || c.stype==-1, "Spline2DUnpack: incorrect C (incorrect parameter C.SType)");
+            if( c.d!=1 )
+            {
+                n = 0;
+                m = 0;
+                return;
+            }
+            n = c.n;
+            m = c.m;
+            tbl = new double[(n-1)*(m-1), 20];
+            sfx = n*m;
+            sfy = 2*n*m;
+            sfxy = 3*n*m;
+            
+            //
+            // Fill
+            //
+            for(i=0; i<=m-2; i++)
+            {
+                for(j=0; j<=n-2; j++)
+                {
+                    p = i*(n-1)+j;
+                    tbl[p,0] = c.x[j];
+                    tbl[p,1] = c.x[j+1];
+                    tbl[p,2] = c.y[i];
+                    tbl[p,3] = c.y[i+1];
+                    dt = 1/(tbl[p,1]-tbl[p,0]);
+                    du = 1/(tbl[p,3]-tbl[p,2]);
+                    
+                    //
+                    // Bilinear interpolation
+                    //
+                    if( c.stype==-1 )
+                    {
+                        for(k=4; k<=19; k++)
+                        {
+                            tbl[p,k] = 0;
+                        }
+                        y1 = c.f[n*i+j];
+                        y2 = c.f[n*i+(j+1)];
+                        y3 = c.f[n*(i+1)+(j+1)];
+                        y4 = c.f[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( c.stype==-3 )
+                    {
+                        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] = c.f[s1];
+                        tbl[p,4+0*4+1] = c.f[sfy+s1]/du;
+                        tbl[p,4+0*4+2] = -(3*c.f[s1])+3*c.f[s4]-2*c.f[sfy+s1]/du-c.f[sfy+s4]/du;
+                        tbl[p,4+0*4+3] = 2*c.f[s1]-2*c.f[s4]+c.f[sfy+s1]/du+c.f[sfy+s4]/du;
+                        tbl[p,4+1*4+0] = c.f[sfx+s1]/dt;
+                        tbl[p,4+1*4+1] = c.f[sfxy+s1]/(dt*du);
+                        tbl[p,4+1*4+2] = -(3*c.f[sfx+s1]/dt)+3*c.f[sfx+s4]/dt-2*c.f[sfxy+s1]/(dt*du)-c.f[sfxy+s4]/(dt*du);
+                        tbl[p,4+1*4+3] = 2*c.f[sfx+s1]/dt-2*c.f[sfx+s4]/dt+c.f[sfxy+s1]/(dt*du)+c.f[sfxy+s4]/(dt*du);
+                        tbl[p,4+2*4+0] = -(3*c.f[s1])+3*c.f[s2]-2*c.f[sfx+s1]/dt-c.f[sfx+s2]/dt;
+                        tbl[p,4+2*4+1] = -(3*c.f[sfy+s1]/du)+3*c.f[sfy+s2]/du-2*c.f[sfxy+s1]/(dt*du)-c.f[sfxy+s2]/(dt*du);
+                        tbl[p,4+2*4+2] = 9*c.f[s1]-9*c.f[s2]+9*c.f[s3]-9*c.f[s4]+6*c.f[sfx+s1]/dt+3*c.f[sfx+s2]/dt-3*c.f[sfx+s3]/dt-6*c.f[sfx+s4]/dt+6*c.f[sfy+s1]/du-6*c.f[sfy+s2]/du-3*c.f[sfy+s3]/du+3*c.f[sfy+s4]/du+4*c.f[sfxy+s1]/(dt*du)+2*c.f[sfxy+s2]/(dt*du)+c.f[sfxy+s3]/(dt*du)+2*c.f[sfxy+s4]/(dt*du);
+                        tbl[p,4+2*4+3] = -(6*c.f[s1])+6*c.f[s2]-6*c.f[s3]+6*c.f[s4]-4*c.f[sfx+s1]/dt-2*c.f[sfx+s2]/dt+2*c.f[sfx+s3]/dt+4*c.f[sfx+s4]/dt-3*c.f[sfy+s1]/du+3*c.f[sfy+s2]/du+3*c.f[sfy+s3]/du-3*c.f[sfy+s4]/du-2*c.f[sfxy+s1]/(dt*du)-c.f[sfxy+s2]/(dt*du)-c.f[sfxy+s3]/(dt*du)-2*c.f[sfxy+s4]/(dt*du);
+                        tbl[p,4+3*4+0] = 2*c.f[s1]-2*c.f[s2]+c.f[sfx+s1]/dt+c.f[sfx+s2]/dt;
+                        tbl[p,4+3*4+1] = 2*c.f[sfy+s1]/du-2*c.f[sfy+s2]/du+c.f[sfxy+s1]/(dt*du)+c.f[sfxy+s2]/(dt*du);
+                        tbl[p,4+3*4+2] = -(6*c.f[s1])+6*c.f[s2]-6*c.f[s3]+6*c.f[s4]-3*c.f[sfx+s1]/dt-3*c.f[sfx+s2]/dt+3*c.f[sfx+s3]/dt+3*c.f[sfx+s4]/dt-4*c.f[sfy+s1]/du+4*c.f[sfy+s2]/du+2*c.f[sfy+s3]/du-2*c.f[sfy+s4]/du-2*c.f[sfxy+s1]/(dt*du)-2*c.f[sfxy+s2]/(dt*du)-c.f[sfxy+s3]/(dt*du)-c.f[sfxy+s4]/(dt*du);
+                        tbl[p,4+3*4+3] = 4*c.f[s1]-4*c.f[s2]+4*c.f[s3]-4*c.f[s4]+2*c.f[sfx+s1]/dt+2*c.f[sfx+s2]/dt-2*c.f[sfx+s3]/dt-2*c.f[sfx+s4]/dt+2*c.f[sfy+s1]/du-2*c.f[sfy+s2]/du-2*c.f[sfy+s3]/du+2*c.f[sfy+s4]/du+c.f[sfxy+s1]/(dt*du)+c.f[sfxy+s2]/(dt*du)+c.f[sfxy+s3]/(dt*du)+c.f[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 creates least squares solver used to  fit  2D  splines  to
+        irregularly sampled (scattered) data.
+
+        Solver object is used to perform spline fits as follows:
+        * solver object is created with spline2dbuildercreate() function
+        * dataset is added with spline2dbuildersetpoints() function
+        * fit area is chosen:
+          * spline2dbuildersetarea()     - for user-defined area
+          * spline2dbuildersetareaauto() - for automatically chosen area
+        * number of grid nodes is chosen with spline2dbuildersetgrid()
+        * prior term is chosen with one of the following functions:
+          * spline2dbuildersetlinterm()   to set linear prior
+          * spline2dbuildersetconstterm() to set constant prior
+          * spline2dbuildersetzeroterm()  to set zero prior
+          * spline2dbuildersetuserterm()  to set user-defined constant prior
+        * solver algorithm is chosen with either:
+          * spline2dbuildersetalgoblocklls() - BlockLLS algorithm, medium-scale problems
+          * spline2dbuildersetalgofastddm()  - FastDDM algorithm, large-scale problems
+        * finally, fitting itself is performed with spline2dfit() function.
+
+        Most of the steps above can be omitted,  solver  is  configured with  good
+        defaults. The minimum is to call:
+        * spline2dbuildercreate() to create solver object
+        * spline2dbuildersetpoints() to specify dataset
+        * spline2dbuildersetgrid() to tell how many nodes you need
+        * spline2dfit() to perform fit
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            D   -   positive number, number of Y-components: D=1 for simple scalar
+                    fit, D>1 for vector-valued spline fitting.
+            
+        OUTPUT PARAMETERS:
+            S   -   solver object
+
+          -- ALGLIB PROJECT --
+             Copyright 29.01.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildercreate(int d,
+            spline2dbuilder state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(d>=1, "Spline2DBuilderCreate: D<=0");
+            
+            //
+            // NOTES:
+            //
+            // 1. Prior term is set to linear one (good default option)
+            // 2. Solver is set to BlockLLS - good enough for small-scale problems.
+            // 3. Refinement rounds: 5; enough to get good convergence.
+            //
+            state.priorterm = 1;
+            state.priortermval = 0;
+            state.areatype = 0;
+            state.gridtype = 0;
+            state.smoothing = 0.0;
+            state.nlayers = 0;
+            state.solvertype = 1;
+            state.npoints = 0;
+            state.d = d;
+            state.sx = 1.0;
+            state.sy = 1.0;
+            state.lsqrcnt = 5;
+            
+            //
+            // Algorithm settings
+            //
+            state.adddegreeoffreedom = true;
+            state.maxcoresize = 16;
+            state.interfacesize = 5;
+        }
+
+
+        /*************************************************************************
+        This function sets constant prior term (model is a sum of  bicubic  spline
+        and global prior, which can be linear, constant, user-defined  constant or
+        zero).
+
+        Constant prior term is determined by least squares fitting.
+
+        INPUT PARAMETERS:
+            S       -   spline builder
+            V       -   value for user-defined prior
+
+          -- ALGLIB --
+             Copyright 01.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildersetuserterm(spline2dbuilder state,
+            double v,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(v), "Spline2DBuilderSetUserTerm: infinite/NAN value passed");
+            state.priorterm = 0;
+            state.priortermval = v;
+        }
+
+
+        /*************************************************************************
+        This function sets linear prior term (model is a sum of bicubic spline and
+        global  prior,  which  can  be  linear, constant, user-defined constant or
+        zero).
+
+        Linear prior term is determined by least squares fitting.
+
+        INPUT PARAMETERS:
+            S       -   spline builder
+
+          -- ALGLIB --
+             Copyright 01.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildersetlinterm(spline2dbuilder state,
+            alglib.xparams _params)
+        {
+            state.priorterm = 1;
+        }
+
+
+        /*************************************************************************
+        This function sets constant prior term (model is a sum of  bicubic  spline
+        and global prior, which can be linear, constant, user-defined  constant or
+        zero).
+
+        Constant prior term is determined by least squares fitting.
+
+        INPUT PARAMETERS:
+            S       -   spline builder
+
+          -- ALGLIB --
+             Copyright 01.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildersetconstterm(spline2dbuilder state,
+            alglib.xparams _params)
+        {
+            state.priorterm = 2;
+        }
+
+
+        /*************************************************************************
+        This function sets zero prior term (model is a sum of bicubic  spline  and
+        global  prior,  which  can  be  linear, constant, user-defined constant or
+        zero).
+
+        INPUT PARAMETERS:
+            S       -   spline builder
+
+          -- ALGLIB --
+             Copyright 01.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildersetzeroterm(spline2dbuilder state,
+            alglib.xparams _params)
+        {
+            state.priorterm = 3;
+        }
+
+
+        /*************************************************************************
+        This function adds dataset to the builder object.
+
+        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       -   spline 2D builder object
+            XY      -   points, array[N,2+D]. One  row  corresponds to  one  point
+                        in the dataset. First 2  elements  are  coordinates,  next
+                        D  elements are function values. Array may  be larger than 
+                        specified, in  this  case  only leading [N,NX+NY] elements 
+                        will be used.
+            N       -   number of points in the dataset
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildersetpoints(spline2dbuilder state,
+            double[,] xy,
+            int n,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int ew = 0;
+
+            alglib.ap.assert(n>0, "Spline2DBuilderSetPoints: N<0");
+            alglib.ap.assert(alglib.ap.rows(xy)>=n, "Spline2DBuilderSetPoints: Rows(XY)<N");
+            alglib.ap.assert(alglib.ap.cols(xy)>=2+state.d, "Spline2DBuilderSetPoints: Cols(XY)<NX+NY");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, n, 2+state.d, _params), "Spline2DBuilderSetPoints: XY contains infinite or NaN values!");
+            state.npoints = n;
+            ew = 2+state.d;
+            apserv.rvectorsetlengthatleast(ref state.xy, n*ew, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=ew-1; j++)
+                {
+                    state.xy[i*ew+j] = xy[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets area where 2D spline interpolant is built. "Auto" means
+        that area extent is determined automatically from dataset extent.
+
+        INPUT PARAMETERS:
+            S       -   spline 2D builder object
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildersetareaauto(spline2dbuilder state,
+            alglib.xparams _params)
+        {
+            state.areatype = 0;
+        }
+
+
+        /*************************************************************************
+        This  function  sets  area  where  2D  spline  interpolant  is   built  to
+        user-defined one: [XA,XB]*[YA,YB]
+
+        INPUT PARAMETERS:
+            S       -   spline 2D builder object
+            XA,XB   -   spatial extent in the first (X) dimension, XA<XB
+            YA,YB   -   spatial extent in the second (Y) dimension, YA<YB
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildersetarea(spline2dbuilder state,
+            double xa,
+            double xb,
+            double ya,
+            double yb,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(xa), "Spline2DBuilderSetArea: XA is not finite");
+            alglib.ap.assert(math.isfinite(xb), "Spline2DBuilderSetArea: XB is not finite");
+            alglib.ap.assert(math.isfinite(ya), "Spline2DBuilderSetArea: YA is not finite");
+            alglib.ap.assert(math.isfinite(yb), "Spline2DBuilderSetArea: YB is not finite");
+            alglib.ap.assert((double)(xa)<(double)(xb), "Spline2DBuilderSetArea: XA>=XB");
+            alglib.ap.assert((double)(ya)<(double)(yb), "Spline2DBuilderSetArea: YA>=YB");
+            state.areatype = 1;
+            state.xa = xa;
+            state.xb = xb;
+            state.ya = ya;
+            state.yb = yb;
+        }
+
+
+        /*************************************************************************
+        This  function  sets  nodes  count  for  2D spline interpolant. Fitting is
+        performed on area defined with one of the "setarea"  functions;  this  one
+        sets number of nodes placed upon the fitting area.
+
+        INPUT PARAMETERS:
+            S       -   spline 2D builder object
+            KX      -   nodes count for the first (X) dimension; fitting  interval
+                        [XA,XB] is separated into KX-1 subintervals, with KX nodes
+                        created at the boundaries.
+            KY      -   nodes count for the first (Y) dimension; fitting  interval
+                        [YA,YB] is separated into KY-1 subintervals, with KY nodes
+                        created at the boundaries.
+
+        NOTE: at  least  4  nodes  is  created in each dimension, so KX and KY are
+              silently increased if needed.
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildersetgrid(spline2dbuilder state,
+            int kx,
+            int ky,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(kx>0, "Spline2DBuilderSetGridSizePrecisely: KX<=0");
+            alglib.ap.assert(ky>0, "Spline2DBuilderSetGridSizePrecisely: KY<=0");
+            state.gridtype = 1;
+            state.kx = Math.Max(kx, 4);
+            state.ky = Math.Max(ky, 4);
+        }
+
+
+        /*************************************************************************
+        This  function  allows  you to choose least squares solver used to perform
+        fitting. This function sets solver algorithm to "FastDDM", which  performs
+        fast parallel fitting by splitting problem into smaller chunks and merging
+        results together.
+
+        This solver is optimized for large-scale problems, starting  from  256x256
+        grids, and up to 10000x10000 grids. Of course, it will  work  for  smaller
+        grids too.
+
+        More detailed description of the algorithm is given below:
+        * algorithm generates hierarchy  of  nested  grids,  ranging  from  ~16x16
+          (topmost "layer" of the model) to ~KX*KY one (final layer). Upper layers
+          model global behavior of the function, lower layers are  used  to  model
+          fine details. Moving from layer to layer doubles grid density.
+        * fitting  is  started  from  topmost  layer, subsequent layers are fitted
+          using residuals from previous ones.
+        * user may choose to skip generation of upper layers and generate  only  a
+          few bottom ones, which  will  result  in  much  better  performance  and
+          parallelization efficiency, at the cost of algorithm inability to "patch"
+          large holes in the dataset.
+        * every layer is regularized using progressively increasing regularization
+          coefficient; thus, increasing  LambdaV  penalizes  fine  details  first,
+          leaving lower frequencies almost intact for a while.
+        * after fitting is done, all layers are merged together into  one  bicubic
+          spline
+          
+        IMPORTANT: regularization coefficient used by  this  solver  is  different
+                   from the one used by  BlockLLS.  Latter  utilizes  nonlinearity
+                   penalty,  which  is  global  in  nature  (large  regularization
+                   results in global linear trend being  extracted);  this  solver
+                   uses another, localized form of penalty, which is suitable  for
+                   parallel processing.
+
+        Notes on memory and performance:
+        * memory requirements: most memory is consumed  during  modeling   of  the
+          higher layers; ~[512*NPoints] bytes is required for a  model  with  full
+          hierarchy of grids being generated. However, if you skip a  few  topmost
+          layers, you will get nearly constant (wrt. points count and  grid  size)
+          memory consumption.
+        * serial running time: O(K*K)+O(NPoints) for a KxK grid
+        * parallelism potential: good. You may get  nearly  linear  speed-up  when
+          performing fitting with just a few layers. Adding more layers results in
+          model becoming more global, which somewhat  reduces  efficiency  of  the
+          parallel code.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            S       -   spline 2D builder object
+            NLayers -   number of layers in the model:
+                        * NLayers>=1 means that up  to  chosen  number  of  bottom
+                          layers is fitted
+                        * NLayers=0 means that maximum number of layers is  chosen
+                          (according to current grid size)
+                        * NLayers<=-1 means that up to |NLayers| topmost layers is
+                          skipped
+                        Recommendations:
+                        * good "default" value is 2 layers
+                        * you may need  more  layers,  if  your  dataset  is  very
+                          irregular and you want to "patch"  large  holes.  For  a
+                          grid step H (equal to AreaWidth/GridSize) you may expect
+                          that last layer reproduces variations at distance H (and
+                          can patch holes that wide); that higher  layers  operate
+                          at distances 2*H, 4*H, 8*H and so on.
+                        * good value for "bullletproof" mode is  NLayers=0,  which
+                          results in complete hierarchy of layers being generated.
+            LambdaV -   regularization coefficient, chosen in such a way  that  it
+                        penalizes bottom layers (fine details) first.
+                        LambdaV>=0, zero value means that no penalty is applied.
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildersetalgofastddm(spline2dbuilder state,
+            int nlayers,
+            double lambdav,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(lambdav), "Spline2DBuilderSetAlgoFastDDM: LambdaV is not finite value");
+            alglib.ap.assert((double)(lambdav)>=(double)(0), "Spline2DBuilderSetAlgoFastDDM: LambdaV<0");
+            state.solvertype = 3;
+            state.nlayers = nlayers;
+            state.smoothing = lambdav;
+        }
+
+
+        /*************************************************************************
+        This  function  allows  you to choose least squares solver used to perform
+        fitting. This function sets solver algorithm to "BlockLLS", which performs
+        least squares fitting  with  fast  sparse  direct  solver,  with  optional
+        nonsmoothness penalty being applied.
+
+        Nonlinearity penalty has the following form:
+
+                                  [                                            ]
+            P() ~ Lambda* integral[ (d2S/dx2)^2 + 2*(d2S/dxdy)^2 + (d2S/dy2)^2 ]dxdy
+                                  [                                            ]
+                          
+        here integral is calculated over entire grid, and "~" means "proportional"
+        because integral is normalized after calcilation. Extremely  large  values
+        of Lambda result in linear fit being performed.
+
+        NOTE: this algorithm is the most robust and controllable one,  but  it  is
+              limited by 512x512 grids and (say) up to 1.000.000 points.  However,
+              ALGLIB has one more  spline  solver:  FastDDM  algorithm,  which  is
+              intended for really large-scale problems (in 10M-100M range). FastDDM
+              algorithm also has better parallelism properties.
+              
+        More information on BlockLLS solver:
+        * memory requirements: ~[32*K^3+256*NPoints]  bytes  for  KxK  grid   with
+          NPoints-sized dataset
+        * serial running time: O(K^4+NPoints)
+        * parallelism potential: limited. You may get some sublinear gain when
+          working with large grids (K's in 256..512 range)
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            S       -   spline 2D builder object
+            LambdaNS-   non-negative value:
+                        * positive value means that some smoothing is applied
+                        * zero value means  that  no  smoothing  is  applied,  and
+                          corresponding entries of design matrix  are  numerically
+                          zero and dropped from consideration.
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildersetalgoblocklls(spline2dbuilder state,
+            double lambdans,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(lambdans), "Spline2DBuilderSetAlgoBlockLLS: LambdaNS is not finite value");
+            alglib.ap.assert((double)(lambdans)>=(double)(0), "Spline2DBuilderSetAlgoBlockLLS: LambdaNS<0");
+            state.solvertype = 1;
+            state.smoothing = lambdans;
+        }
+
+
+        /*************************************************************************
+        This  function  allows  you to choose least squares solver used to perform
+        fitting. This function sets solver algorithm to "NaiveLLS".
+
+        IMPORTANT: NaiveLLS is NOT intended to be used in  real  life  code!  This
+                   algorithm solves problem by generated dense (K^2)x(K^2+NPoints)
+                   matrix and solves  linear  least  squares  problem  with  dense
+                   solver.
+                   
+                   It is here just  to  test  BlockLLS  against  reference  solver
+                   (and maybe for someone trying to compare well optimized  solver
+                   against straightforward approach to the LLS problem).
+
+        More information on naive LLS solver:
+        * memory requirements: ~[8*K^4+256*NPoints] bytes for KxK grid.
+        * serial running time: O(K^6+NPoints) for KxK grid
+        * when compared with BlockLLS,  NaiveLLS  has ~K  larger memory demand and
+          ~K^2  larger running time.
+
+        INPUT PARAMETERS:
+            S       -   spline 2D builder object
+            LambdaNS-   nonsmoothness penalty
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dbuildersetalgonaivells(spline2dbuilder state,
+            double lambdans,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(lambdans), "Spline2DBuilderSetAlgoBlockLLS: LambdaNS is not finite value");
+            alglib.ap.assert((double)(lambdans)>=(double)(0), "Spline2DBuilderSetAlgoBlockLLS: LambdaNS<0");
+            state.solvertype = 2;
+            state.smoothing = lambdans;
+        }
+
+
+        /*************************************************************************
+        This function fits bicubic spline to current dataset, using current  area/
+        grid and current LLS solver.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            State   -   spline 2D builder object
+
+        OUTPUT PARAMETERS:
+            S       -   2D spline, fit result
+            Rep     -   fitting report, which provides some additional info  about
+                        errors, R2 coefficient and so on.
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dfit(spline2dbuilder state,
+            spline2dinterpolant s,
+            spline2dfitreport rep,
+            alglib.xparams _params)
+        {
+            double xa = 0;
+            double xb = 0;
+            double ya = 0;
+            double yb = 0;
+            double xaraw = 0;
+            double xbraw = 0;
+            double yaraw = 0;
+            double ybraw = 0;
+            int kx = 0;
+            int ky = 0;
+            double hx = 0;
+            double hy = 0;
+            double invhx = 0;
+            double invhy = 0;
+            int gridexpansion = 0;
+            int nzwidth = 0;
+            int bfrad = 0;
+            int npoints = 0;
+            int d = 0;
+            int ew = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            int k0 = 0;
+            int k1 = 0;
+            double vx = 0;
+            double vy = 0;
+            int arows = 0;
+            int acopied = 0;
+            int basecasex = 0;
+            int basecasey = 0;
+            double eps = 0;
+            double[] xywork = new double[0];
+            double[,] vterm = new double[0,0];
+            double[] tmpx = new double[0];
+            double[] tmpy = new double[0];
+            double[] tmp0 = new double[0];
+            double[] tmp1 = new double[0];
+            double[] meany = new double[0];
+            int[] xyindex = new int[0];
+            int[] tmpi = new int[0];
+            spline1d.spline1dinterpolant basis1 = new spline1d.spline1dinterpolant();
+            sparse.sparsematrix av = new sparse.sparsematrix();
+            sparse.sparsematrix ah = new sparse.sparsematrix();
+            spline2dxdesignmatrix xdesignmatrix = new spline2dxdesignmatrix();
+            double[] z = new double[0];
+            spline2dblockllsbuf blockllsbuf = new spline2dblockllsbuf();
+            int sfx = 0;
+            int sfy = 0;
+            int sfxy = 0;
+            double tss = 0;
+            int dstidx = 0;
+
+            nzwidth = 4;
+            bfrad = 2;
+            npoints = state.npoints;
+            d = state.d;
+            ew = 2+d;
+            
+            //
+            // Integrity checks
+            //
+            alglib.ap.assert((double)(state.sx)==(double)(1), "Spline2DFit: integrity error");
+            alglib.ap.assert((double)(state.sy)==(double)(1), "Spline2DFit: integrity error");
+            
+            //
+            // Determine actual area size and grid step
+            //
+            // NOTE: initialize vars by zeros in order to avoid spurious
+            //       compiler warnings.
+            //
+            xa = 0;
+            xb = 0;
+            ya = 0;
+            yb = 0;
+            if( state.areatype==0 )
+            {
+                if( npoints>0 )
+                {
+                    xa = state.xy[0];
+                    xb = state.xy[0];
+                    ya = state.xy[1];
+                    yb = state.xy[1];
+                    for(i=1; i<=npoints-1; i++)
+                    {
+                        xa = Math.Min(xa, state.xy[i*ew+0]);
+                        xb = Math.Max(xb, state.xy[i*ew+0]);
+                        ya = Math.Min(ya, state.xy[i*ew+1]);
+                        yb = Math.Max(yb, state.xy[i*ew+1]);
+                    }
+                }
+                else
+                {
+                    xa = -1;
+                    xb = 1;
+                    ya = -1;
+                    yb = 1;
+                }
+            }
+            else
+            {
+                if( state.areatype==1 )
+                {
+                    xa = state.xa;
+                    xb = state.xb;
+                    ya = state.ya;
+                    yb = state.yb;
+                }
+                else
+                {
+                    alglib.ap.assert(false);
+                }
+            }
+            if( (double)(xa)==(double)(xb) )
+            {
+                v = xa;
+                if( (double)(v)>=(double)(0) )
+                {
+                    xa = v/2-1;
+                    xb = v*2+1;
+                }
+                else
+                {
+                    xa = v*2-1;
+                    xb = v/2+1;
+                }
+            }
+            if( (double)(ya)==(double)(yb) )
+            {
+                v = ya;
+                if( (double)(v)>=(double)(0) )
+                {
+                    ya = v/2-1;
+                    yb = v*2+1;
+                }
+                else
+                {
+                    ya = v*2-1;
+                    yb = v/2+1;
+                }
+            }
+            alglib.ap.assert((double)(xa)<(double)(xb), "Spline2DFit: integrity error");
+            alglib.ap.assert((double)(ya)<(double)(yb), "Spline2DFit: integrity error");
+            kx = 0;
+            ky = 0;
+            if( state.gridtype==0 )
+            {
+                kx = 4;
+                ky = 4;
+            }
+            else
+            {
+                if( state.gridtype==1 )
+                {
+                    kx = state.kx;
+                    ky = state.ky;
+                }
+                else
+                {
+                    alglib.ap.assert(false);
+                }
+            }
+            alglib.ap.assert(kx>0, "Spline2DFit: integrity error");
+            alglib.ap.assert(ky>0, "Spline2DFit: integrity error");
+            basecasex = -1;
+            basecasey = -1;
+            if( state.solvertype==3 )
+            {
+                
+                //
+                // Large-scale solver with special requirements to grid size.
+                //
+                kx = Math.Max(kx, nzwidth);
+                ky = Math.Max(ky, nzwidth);
+                k = 1;
+                while( apserv.imin2(kx, ky, _params)>state.maxcoresize+1 )
+                {
+                    kx = apserv.idivup(kx-1, 2, _params)+1;
+                    ky = apserv.idivup(ky-1, 2, _params)+1;
+                    k = k+1;
+                }
+                basecasex = kx-1;
+                k0 = 1;
+                while( kx>state.maxcoresize+1 )
+                {
+                    basecasex = apserv.idivup(kx-1, 2, _params);
+                    kx = basecasex+1;
+                    k0 = k0+1;
+                }
+                while( k0>1 )
+                {
+                    kx = (kx-1)*2+1;
+                    k0 = k0-1;
+                }
+                basecasey = ky-1;
+                k1 = 1;
+                while( ky>state.maxcoresize+1 )
+                {
+                    basecasey = apserv.idivup(ky-1, 2, _params);
+                    ky = basecasey+1;
+                    k1 = k1+1;
+                }
+                while( k1>1 )
+                {
+                    ky = (ky-1)*2+1;
+                    k1 = k1-1;
+                }
+                while( k>1 )
+                {
+                    kx = (kx-1)*2+1;
+                    ky = (ky-1)*2+1;
+                    k = k-1;
+                }
+                
+                //
+                // Grid is NOT expanded. We have very strict requirements on
+                // grid size, and we do not want to overcomplicate it by
+                // playing with grid size in order to add one more degree of
+                // freedom. It is not relevant for such large tasks.
+                //
+                gridexpansion = 0;
+            }
+            else
+            {
+                
+                //
+                // Medium-scale solvers which are tolerant to grid size.
+                //
+                kx = Math.Max(kx, nzwidth);
+                ky = Math.Max(ky, nzwidth);
+                
+                //
+                // Grid is expanded by 1 in order to add one more effective degree
+                // of freedom to the spline. Having additional nodes outside of the
+                // area allows us to emulate changes in the derivative at the bound
+                // without having specialized "boundary" version of the basis function.
+                //
+                if( state.adddegreeoffreedom )
+                {
+                    gridexpansion = 1;
+                }
+                else
+                {
+                    gridexpansion = 0;
+                }
+            }
+            hx = apserv.coalesce(xb-xa, 1.0, _params)/(kx-1);
+            hy = apserv.coalesce(yb-ya, 1.0, _params)/(ky-1);
+            invhx = 1/hx;
+            invhy = 1/hy;
+            
+            //
+            // We determined "raw" grid size. Now perform a grid correction according
+            // to current grid expansion size.
+            //
+            xaraw = xa;
+            yaraw = ya;
+            xbraw = xb;
+            ybraw = yb;
+            xa = xa-hx*gridexpansion;
+            ya = ya-hy*gridexpansion;
+            xb = xb+hx*gridexpansion;
+            yb = yb+hy*gridexpansion;
+            kx = kx+2*gridexpansion;
+            ky = ky+2*gridexpansion;
+            
+            //
+            // Create output spline using transformed (unit-scale)
+            // coordinates, fill by zero values
+            //
+            s.d = d;
+            s.n = kx;
+            s.m = ky;
+            s.stype = -3;
+            sfx = s.n*s.m*d;
+            sfy = 2*s.n*s.m*d;
+            sfxy = 3*s.n*s.m*d;
+            s.x = new double[s.n];
+            s.y = new double[s.m];
+            s.f = new double[4*s.n*s.m*d];
+            for(i=0; i<=s.n-1; i++)
+            {
+                s.x[i] = i;
+            }
+            for(i=0; i<=s.m-1; i++)
+            {
+                s.y[i] = i;
+            }
+            for(i=0; i<=4*s.n*s.m*d-1; i++)
+            {
+                s.f[i] = 0.0;
+            }
+            
+            //
+            // Create local copy of dataset (only points in the grid are copied;
+            // we allow small step out of the grid, by Eps*H, in order to deal
+            // with numerical rounding errors).
+            //
+            // An additional copy of Y-values is created at columns beyond 2+J;
+            // it is preserved during all transformations. This copy is used
+            // to calculate error-related metrics.
+            //
+            // Calculate mean(Y), TSS
+            //
+            meany = new double[d];
+            for(j=0; j<=d-1; j++)
+            {
+                meany[j] = 0;
+            }
+            apserv.rvectorsetlengthatleast(ref xywork, npoints*ew, _params);
+            acopied = 0;
+            eps = 1.0E-6;
+            for(i=0; i<=npoints-1; i++)
+            {
+                vx = state.xy[i*ew+0];
+                vy = state.xy[i*ew+1];
+                if( (((double)(xaraw-eps*hx)<=(double)(vx) && (double)(vx)<=(double)(xbraw+eps*hx)) && (double)(yaraw-eps*hy)<=(double)(vy)) && (double)(vy)<=(double)(ybraw+eps*hy) )
+                {
+                    xywork[acopied*ew+0] = (vx-xa)*invhx;
+                    xywork[acopied*ew+1] = (vy-ya)*invhy;
+                    for(j=0; j<=d-1; j++)
+                    {
+                        v = state.xy[i*ew+2+j];
+                        xywork[acopied*ew+2+j] = v;
+                        meany[j] = meany[j]+v;
+                    }
+                    acopied = acopied+1;
+                }
+            }
+            npoints = acopied;
+            for(j=0; j<=d-1; j++)
+            {
+                meany[j] = meany[j]/apserv.coalesce(npoints, 1, _params);
+            }
+            tss = 0.0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=d-1; j++)
+                {
+                    tss = tss+math.sqr(xywork[i*ew+2+j]-meany[j]);
+                }
+            }
+            tss = apserv.coalesce(tss, 1.0, _params);
+            
+            //
+            // Handle prior term.
+            // Modify output spline.
+            // Quick exit if dataset is empty.
+            //
+            intfitserv.buildpriorterm1(xywork, npoints, 2, d, state.priorterm, state.priortermval, ref vterm, _params);
+            if( npoints==0 )
+            {
+                
+                //
+                // Quick exit
+                //
+                for(k=0; k<=s.n*s.m-1; k++)
+                {
+                    k0 = k%s.n;
+                    k1 = k/s.n;
+                    for(j=0; j<=d-1; j++)
+                    {
+                        dstidx = d*(k1*s.n+k0)+j;
+                        s.f[dstidx] = s.f[dstidx]+vterm[j,0]*s.x[k0]+vterm[j,1]*s.y[k1]+vterm[j,2];
+                        s.f[sfx+dstidx] = s.f[sfx+dstidx]+vterm[j,0];
+                        s.f[sfy+dstidx] = s.f[sfy+dstidx]+vterm[j,1];
+                    }
+                }
+                for(i=0; i<=s.n-1; i++)
+                {
+                    s.x[i] = s.x[i]*hx+xa;
+                }
+                for(i=0; i<=s.m-1; i++)
+                {
+                    s.y[i] = s.y[i]*hy+ya;
+                }
+                for(i=0; i<=s.n*s.m*d-1; i++)
+                {
+                    s.f[sfx+i] = s.f[sfx+i]*invhx;
+                    s.f[sfy+i] = s.f[sfy+i]*invhy;
+                    s.f[sfxy+i] = s.f[sfxy+i]*invhx*invhy;
+                }
+                rep.rmserror = 0;
+                rep.avgerror = 0;
+                rep.maxerror = 0;
+                rep.r2 = 1.0;
+                return;
+            }
+            
+            //
+            // Build 1D compact basis function
+            // Generate design matrix
+            //
+            tmpx = new double[7];
+            tmpy = new double[7];
+            tmpx[0] = -3;
+            tmpx[1] = -2;
+            tmpx[2] = -1;
+            tmpx[3] = 0;
+            tmpx[4] = 1;
+            tmpx[5] = 2;
+            tmpx[6] = 3;
+            tmpy[0] = 0;
+            tmpy[1] = 0;
+            tmpy[2] = (double)1/(double)12;
+            tmpy[3] = (double)2/(double)6;
+            tmpy[4] = (double)1/(double)12;
+            tmpy[5] = 0;
+            tmpy[6] = 0;
+            spline1d.spline1dbuildcubic(tmpx, tmpy, alglib.ap.len(tmpx), 2, 0.0, 2, 0.0, basis1, _params);
+            
+            //
+            // Solve.
+            // Update spline.
+            //
+            if( state.solvertype==1 )
+            {
+                
+                //
+                // BlockLLS
+                //
+                reorderdatasetandbuildindex(xywork, npoints, d, tmp0, 0, kx, ky, ref xyindex, ref tmpi, _params);
+                xdesigngenerate(xywork, xyindex, 0, kx, kx, 0, ky, ky, d, lambdaregblocklls, state.smoothing, basis1, xdesignmatrix, _params);
+                blockllsfit(xdesignmatrix, state.lsqrcnt, ref z, rep, tss, blockllsbuf, _params);
+                updatesplinetable(z, kx, ky, d, basis1, bfrad, s.f, s.m, s.n, 1, _params);
+            }
+            else
+            {
+                if( state.solvertype==2 )
+                {
+                    
+                    //
+                    // NaiveLLS, reference implementation
+                    //
+                    generatedesignmatrix(xywork, npoints, d, kx, ky, state.smoothing, lambdaregblocklls, basis1, av, ah, ref arows, _params);
+                    naivellsfit(av, ah, arows, xywork, kx, ky, npoints, d, state.lsqrcnt, ref z, rep, tss, _params);
+                    updatesplinetable(z, kx, ky, d, basis1, bfrad, s.f, s.m, s.n, 1, _params);
+                }
+                else
+                {
+                    if( state.solvertype==3 )
+                    {
+                        
+                        //
+                        // FastDDM method
+                        //
+                        alglib.ap.assert(basecasex>0, "Spline2DFit: integrity error");
+                        alglib.ap.assert(basecasey>0, "Spline2DFit: integrity error");
+                        fastddmfit(xywork, npoints, d, kx, ky, basecasex, basecasey, state.maxcoresize, state.interfacesize, state.nlayers, state.smoothing, state.lsqrcnt, basis1, s, rep, tss, _params);
+                    }
+                    else
+                    {
+                        alglib.ap.assert(false, "Spline2DFit: integrity error");
+                    }
+                }
+            }
+            
+            //
+            // Append prior term.
+            // Transform spline to original coordinates
+            //
+            for(k=0; k<=s.n*s.m-1; k++)
+            {
+                k0 = k%s.n;
+                k1 = k/s.n;
+                for(j=0; j<=d-1; j++)
+                {
+                    dstidx = d*(k1*s.n+k0)+j;
+                    s.f[dstidx] = s.f[dstidx]+vterm[j,0]*s.x[k0]+vterm[j,1]*s.y[k1]+vterm[j,2];
+                    s.f[sfx+dstidx] = s.f[sfx+dstidx]+vterm[j,0];
+                    s.f[sfy+dstidx] = s.f[sfy+dstidx]+vterm[j,1];
+                }
+            }
+            for(i=0; i<=s.n-1; i++)
+            {
+                s.x[i] = s.x[i]*hx+xa;
+            }
+            for(i=0; i<=s.m-1; i++)
+            {
+                s.y[i] = s.y[i]*hy+ya;
+            }
+            for(i=0; i<=s.n*s.m*d-1; i++)
+            {
+                s.f[sfx+i] = s.f[sfx+i]*invhx;
+                s.f[sfy+i] = s.f[sfy+i]*invhy;
+                s.f[sfxy+i] = s.f[sfxy+i]*invhx*invhy;
+            }
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 28.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dalloc(alglib.serializer s,
+            spline2dinterpolant spline,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Header
+            //
+            s.alloc_entry();
+            
+            //
+            // Data
+            //
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            apserv.allocrealarray(s, spline.x, -1, _params);
+            apserv.allocrealarray(s, spline.y, -1, _params);
+            apserv.allocrealarray(s, spline.f, -1, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 28.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dserialize(alglib.serializer s,
+            spline2dinterpolant spline,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Header
+            //
+            s.serialize_int(scodes.getspline2dserializationcode(_params));
+            
+            //
+            // Data
+            //
+            s.serialize_int(spline.stype);
+            s.serialize_int(spline.n);
+            s.serialize_int(spline.m);
+            s.serialize_int(spline.d);
+            apserv.serializerealarray(s, spline.x, -1, _params);
+            apserv.serializerealarray(s, spline.y, -1, _params);
+            apserv.serializerealarray(s, spline.f, -1, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 28.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spline2dunserialize(alglib.serializer s,
+            spline2dinterpolant spline,
+            alglib.xparams _params)
+        {
+            int scode = 0;
+
+            
+            //
+            // Header
+            //
+            scode = s.unserialize_int();
+            alglib.ap.assert(scode==scodes.getspline2dserializationcode(_params), "Spline2DUnserialize: stream header corrupted");
+            
+            //
+            // Data
+            //
+            spline.stype = s.unserialize_int();
+            spline.n = s.unserialize_int();
+            spline.m = s.unserialize_int();
+            spline.d = s.unserialize_int();
+            apserv.unserializerealarray(s, ref spline.x, _params);
+            apserv.unserializerealarray(s, ref spline.y, _params);
+            apserv.unserializerealarray(s, ref spline.f, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                for(j=0; j<=n-1; j++)
+                {
+                    spline1d.spline1ddiff(c, x[j], ref s, ref ds, ref d2s, _params);
+                    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, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    spline1d.spline1ddiff(c, y[i], ref s, ref ds, ref d2s, _params);
+                    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, _params);
+                for(j=0; j<=n-1; j++)
+                {
+                    spline1d.spline1ddiff(c, x[j], ref s, ref ds, ref d2s, _params);
+                    dxy[i,j] = ds;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function generates design matrix for the problem (in fact, two design
+        matrices are generated: "vertical" one and transposed (horizontal) one.
+
+        INPUT PARAMETERS:
+            XY          -   array[NPoints*(2+D)]; dataset after scaling  in  such
+                            way that grid step is equal to 1.0 in both dimensions.
+            NPoints     -   dataset size, NPoints>=1
+            KX, KY      -   grid size, KX,KY>=4
+            Smoothing   -   nonlinearity penalty coefficient, >=0
+            LambdaReg   -   regularization coefficient, >=0
+            Basis1      -   basis spline, expected to be non-zero only at [-2,+2]
+            AV, AH      -   possibly preallocated buffers
+
+        OUTPUT PARAMETERS:
+            AV          -   sparse matrix[ARows,KX*KY]; design matrix
+            AH          -   transpose of AV
+            ARows       -   number of rows in design matrix
+            
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void generatedesignmatrix(double[] xy,
+            int npoints,
+            int d,
+            int kx,
+            int ky,
+            double smoothing,
+            double lambdareg,
+            spline1d.spline1dinterpolant basis1,
+            sparse.sparsematrix av,
+            sparse.sparsematrix ah,
+            ref int arows,
+            alglib.xparams _params)
+        {
+            int nzwidth = 0;
+            int nzshift = 0;
+            int ew = 0;
+            int i = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int k0 = 0;
+            int k1 = 0;
+            int dstidx = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double w0 = 0;
+            double w1 = 0;
+            double w2 = 0;
+            int[] crx = new int[0];
+            int[] cry = new int[0];
+            int[] nrs = new int[0];
+            double[,] d2x = new double[0,0];
+            double[,] d2y = new double[0,0];
+            double[,] dxy = new double[0,0];
+
+            arows = 0;
+
+            nzwidth = 4;
+            nzshift = 1;
+            alglib.ap.assert(npoints>0, "Spline2DFit: integrity check failed");
+            alglib.ap.assert(kx>=nzwidth, "Spline2DFit: integrity check failed");
+            alglib.ap.assert(ky>=nzwidth, "Spline2DFit: integrity check failed");
+            ew = 2+d;
+            
+            //
+            // Determine canonical rectangle for every point. Every point of the dataset is
+            // influenced by at most NZWidth*NZWidth basis functions, which form NZWidth*NZWidth
+            // canonical rectangle.
+            //
+            // Thus, we have (KX-NZWidth+1)*(KY-NZWidth+1) overlapping canonical rectangles.
+            // Assigning every point to its rectangle simplifies creation of sparse basis
+            // matrix at the next steps.
+            //
+            crx = new int[npoints];
+            cry = new int[npoints];
+            for(i=0; i<=npoints-1; i++)
+            {
+                crx[i] = apserv.iboundval((int)Math.Floor(xy[i*ew+0])-nzshift, 0, kx-nzwidth, _params);
+                cry[i] = apserv.iboundval((int)Math.Floor(xy[i*ew+1])-nzshift, 0, ky-nzwidth, _params);
+            }
+            
+            //
+            // Create vertical and horizontal design matrices 
+            //
+            arows = npoints+kx*ky;
+            if( (double)(smoothing)!=(double)(0.0) )
+            {
+                alglib.ap.assert((double)(smoothing)>(double)(0.0), "Spline2DFit: integrity check failed");
+                arows = arows+3*(kx-2)*(ky-2);
+            }
+            nrs = new int[arows];
+            dstidx = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                nrs[dstidx+i] = nzwidth*nzwidth;
+            }
+            dstidx = dstidx+npoints;
+            for(i=0; i<=kx*ky-1; i++)
+            {
+                nrs[dstidx+i] = 1;
+            }
+            dstidx = dstidx+kx*ky;
+            if( (double)(smoothing)!=(double)(0.0) )
+            {
+                for(i=0; i<=3*(kx-2)*(ky-2)-1; i++)
+                {
+                    nrs[dstidx+i] = 3*3;
+                }
+                dstidx = dstidx+3*(kx-2)*(ky-2);
+            }
+            alglib.ap.assert(dstidx==arows, "Spline2DFit: integrity check failed");
+            sparse.sparsecreatecrs(arows, kx*ky, nrs, av, _params);
+            dstidx = 0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j1=0; j1<=nzwidth-1; j1++)
+                {
+                    for(j0=0; j0<=nzwidth-1; j0++)
+                    {
+                        v0 = spline1d.spline1dcalc(basis1, xy[i*ew+0]-(crx[i]+j0), _params);
+                        v1 = spline1d.spline1dcalc(basis1, xy[i*ew+1]-(cry[i]+j1), _params);
+                        sparse.sparseset(av, dstidx+i, (cry[i]+j1)*kx+(crx[i]+j0), v0*v1, _params);
+                    }
+                }
+            }
+            dstidx = dstidx+npoints;
+            for(i=0; i<=kx*ky-1; i++)
+            {
+                sparse.sparseset(av, dstidx+i, i, lambdareg, _params);
+            }
+            dstidx = dstidx+kx*ky;
+            if( (double)(smoothing)!=(double)(0.0) )
+            {
+                
+                //
+                // Smoothing is applied. Because all grid nodes are same,
+                // we apply same smoothing kernel, which is calculated only
+                // once at the beginning of design matrix generation.
+                //
+                d2x = new double[3, 3];
+                d2y = new double[3, 3];
+                dxy = new double[3, 3];
+                for(j1=0; j1<=2; j1++)
+                {
+                    for(j0=0; j0<=2; j0++)
+                    {
+                        d2x[j0,j1] = 0.0;
+                        d2y[j0,j1] = 0.0;
+                        dxy[j0,j1] = 0.0;
+                    }
+                }
+                for(k1=0; k1<=2; k1++)
+                {
+                    for(k0=0; k0<=2; k0++)
+                    {
+                        spline1d.spline1ddiff(basis1, -(k0-1), ref v0, ref v1, ref v2, _params);
+                        spline1d.spline1ddiff(basis1, -(k1-1), ref w0, ref w1, ref w2, _params);
+                        d2x[k0,k1] = d2x[k0,k1]+v2*w0;
+                        d2y[k0,k1] = d2y[k0,k1]+w2*v0;
+                        dxy[k0,k1] = dxy[k0,k1]+v1*w1;
+                    }
+                }
+                
+                //
+                // Now, kernel is ready - apply it to all inner nodes of the grid.
+                //
+                for(j1=1; j1<=ky-2; j1++)
+                {
+                    for(j0=1; j0<=kx-2; j0++)
+                    {
+                        
+                        //
+                        // d2F/dx2 term
+                        //
+                        v = smoothing;
+                        for(k1=-1; k1<=1; k1++)
+                        {
+                            for(k0=-1; k0<=1; k0++)
+                            {
+                                sparse.sparseset(av, dstidx, (j1+k1)*kx+(j0+k0), v*d2x[1+k0,1+k1], _params);
+                            }
+                        }
+                        dstidx = dstidx+1;
+                        
+                        //
+                        // d2F/dy2 term
+                        //
+                        v = smoothing;
+                        for(k1=-1; k1<=1; k1++)
+                        {
+                            for(k0=-1; k0<=1; k0++)
+                            {
+                                sparse.sparseset(av, dstidx, (j1+k1)*kx+(j0+k0), v*d2y[1+k0,1+k1], _params);
+                            }
+                        }
+                        dstidx = dstidx+1;
+                        
+                        //
+                        // 2*d2F/dxdy term
+                        //
+                        v = Math.Sqrt(2)*smoothing;
+                        for(k1=-1; k1<=1; k1++)
+                        {
+                            for(k0=-1; k0<=1; k0++)
+                            {
+                                sparse.sparseset(av, dstidx, (j1+k1)*kx+(j0+k0), v*dxy[1+k0,1+k1], _params);
+                            }
+                        }
+                        dstidx = dstidx+1;
+                    }
+                }
+            }
+            alglib.ap.assert(dstidx==arows, "Spline2DFit: integrity check failed");
+            sparse.sparsecopy(av, ah, _params);
+            sparse.sparsetransposecrs(ah, _params);
+        }
+
+
+        /*************************************************************************
+        This function updates table of spline values/derivatives using coefficients
+        for a layer of basis functions.
+            
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void updatesplinetable(double[] z,
+            int kx,
+            int ky,
+            int d,
+            spline1d.spline1dinterpolant basis1,
+            int bfrad,
+            double[] ftbl,
+            int m,
+            int n,
+            int scalexy,
+            alglib.xparams _params)
+        {
+            int k = 0;
+            int k0 = 0;
+            int k1 = 0;
+            int j = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int j0a = 0;
+            int j0b = 0;
+            int j1a = 0;
+            int j1b = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v01 = 0;
+            double v11 = 0;
+            double rdummy = 0;
+            int dstidx = 0;
+            int sfx = 0;
+            int sfy = 0;
+            int sfxy = 0;
+            double invscalexy = 0;
+
+            alglib.ap.assert(n==(kx-1)*scalexy+1, "Spline2DFit.UpdateSplineTable: integrity check failed");
+            alglib.ap.assert(m==(ky-1)*scalexy+1, "Spline2DFit.UpdateSplineTable: integrity check failed");
+            invscalexy = (double)1/(double)scalexy;
+            sfx = n*m*d;
+            sfy = 2*n*m*d;
+            sfxy = 3*n*m*d;
+            for(k=0; k<=kx*ky-1; k++)
+            {
+                k0 = k%kx;
+                k1 = k/kx;
+                j0a = apserv.iboundval(k0*scalexy-(bfrad*scalexy-1), 0, n-1, _params);
+                j0b = apserv.iboundval(k0*scalexy+(bfrad*scalexy-1), 0, n-1, _params);
+                j1a = apserv.iboundval(k1*scalexy-(bfrad*scalexy-1), 0, m-1, _params);
+                j1b = apserv.iboundval(k1*scalexy+(bfrad*scalexy-1), 0, m-1, _params);
+                for(j1=j1a; j1<=j1b; j1++)
+                {
+                    spline1d.spline1ddiff(basis1, (j1-k1*scalexy)*invscalexy, ref v1, ref v11, ref rdummy, _params);
+                    v11 = v11*invscalexy;
+                    for(j0=j0a; j0<=j0b; j0++)
+                    {
+                        spline1d.spline1ddiff(basis1, (j0-k0*scalexy)*invscalexy, ref v0, ref v01, ref rdummy, _params);
+                        v01 = v01*invscalexy;
+                        for(j=0; j<=d-1; j++)
+                        {
+                            dstidx = d*(j1*n+j0)+j;
+                            v = z[j*kx*ky+k];
+                            ftbl[dstidx] = ftbl[dstidx]+v0*v1*v;
+                            ftbl[sfx+dstidx] = ftbl[sfx+dstidx]+v01*v1*v;
+                            ftbl[sfy+dstidx] = ftbl[sfy+dstidx]+v0*v11*v;
+                            ftbl[sfxy+dstidx] = ftbl[sfxy+dstidx]+v01*v11*v;
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function performs fitting with FastDDM solver.
+        Internal function, never use it directly.
+
+        INPUT PARAMETERS:
+            XY          -   array[NPoints*(2+D)], dataset; destroyed in process
+            KX, KY      -   grid size
+            TileSize    -   tile size
+            InterfaceSize-  interface size
+            NPoints     -   points count
+            D           -   number of components in vector-valued spline, D>=1
+            LSQRCnt     -   number of iterations, non-zero:
+                            * LSQRCnt>0 means that specified amount of  preconditioned
+                              LSQR  iterations  will  be  performed  to solve problem;
+                              usually  we  need  2..5  its.  Recommended option - best
+                              convergence and stability/quality.
+                            * LSQRCnt<0 means that instead of LSQR  we  use  iterative
+                              refinement on normal equations. Again, 2..5 its is enough.
+            Basis1      -   basis spline, expected to be non-zero only at [-2,+2]
+            Z           -   possibly preallocated buffer for solution
+            Residuals   -   possibly preallocated buffer for residuals at dataset points
+            Rep         -   report structure; fields which are not set by this function
+                            are left intact
+            TSS         -   total sum of squares; used to calculate R2
+            
+
+        OUTPUT PARAMETERS:
+            XY          -   destroyed in process
+            Z           -   array[KX*KY*D], filled by solution; KX*KY coefficients
+                            corresponding to each of D dimensions are stored contiguously.
+            Rep         -   following fields are set:
+                            * Rep.RMSError
+                            * Rep.AvgError
+                            * Rep.MaxError
+                            * Rep.R2
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void fastddmfit(double[] xy,
+            int npoints,
+            int d,
+            int kx,
+            int ky,
+            int basecasex,
+            int basecasey,
+            int maxcoresize,
+            int interfacesize,
+            int nlayers,
+            double smoothing,
+            int lsqrcnt,
+            spline1d.spline1dinterpolant basis1,
+            spline2dinterpolant spline,
+            spline2dfitreport rep,
+            double tss,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int nzwidth = 0;
+            int xew = 0;
+            int ntotallayers = 0;
+            int scaleidx = 0;
+            int scalexy = 0;
+            double invscalexy = 0;
+            int kxcur = 0;
+            int kycur = 0;
+            int tilescount0 = 0;
+            int tilescount1 = 0;
+            double v = 0;
+            double rss = 0;
+            double[] yraw = new double[0];
+            int[] xyindex = new int[0];
+            double[] tmp0 = new double[0];
+            int[] bufi = new int[0];
+            spline2dfastddmbuf seed = new spline2dfastddmbuf();
+            alglib.smp.shared_pool pool = new alglib.smp.shared_pool();
+            spline2dxdesignmatrix xdesignmatrix = new spline2dxdesignmatrix();
+            spline2dblockllsbuf blockllsbuf = new spline2dblockllsbuf();
+            spline2dfitreport dummyrep = new spline2dfitreport();
+
+            
+            //
+            // Dataset metrics and integrity checks
+            //
+            nzwidth = 4;
+            xew = 2+d;
+            alglib.ap.assert(maxcoresize>=2, "Spline2DFit: integrity check failed");
+            alglib.ap.assert(interfacesize>=1, "Spline2DFit: integrity check failed");
+            alglib.ap.assert(kx>=nzwidth, "Spline2DFit: integrity check failed");
+            alglib.ap.assert(ky>=nzwidth, "Spline2DFit: integrity check failed");
+            
+            //
+            // Verify consistency of the grid size (KX,KY) with basecase sizes.
+            // Determine full number of layers.
+            //
+            alglib.ap.assert(basecasex<=maxcoresize, "Spline2DFit: integrity error");
+            alglib.ap.assert(basecasey<=maxcoresize, "Spline2DFit: integrity error");
+            ntotallayers = 1;
+            scalexy = 1;
+            kxcur = kx;
+            kycur = ky;
+            while( kxcur>basecasex+1 && kycur>basecasey+1 )
+            {
+                alglib.ap.assert(kxcur%2==1, "Spline2DFit: integrity error");
+                alglib.ap.assert(kycur%2==1, "Spline2DFit: integrity error");
+                kxcur = (kxcur-1)/2+1;
+                kycur = (kycur-1)/2+1;
+                scalexy = scalexy*2;
+                apserv.inc(ref ntotallayers, _params);
+            }
+            invscalexy = (double)1/(double)scalexy;
+            alglib.ap.assert((kxcur<=maxcoresize+1 && kxcur==basecasex+1) || kxcur%basecasex==1, "Spline2DFit: integrity error");
+            alglib.ap.assert((kycur<=maxcoresize+1 && kycur==basecasey+1) || kycur%basecasey==1, "Spline2DFit: integrity error");
+            alglib.ap.assert(kxcur==basecasex+1 || kycur==basecasey+1, "Spline2DFit: integrity error");
+            
+            //
+            // Initial scaling of dataset.
+            // Store original target values to YRaw.
+            //
+            apserv.rvectorsetlengthatleast(ref yraw, npoints*d, _params);
+            for(i=0; i<=npoints-1; i++)
+            {
+                xy[xew*i+0] = xy[xew*i+0]*invscalexy;
+                xy[xew*i+1] = xy[xew*i+1]*invscalexy;
+                for(j=0; j<=d-1; j++)
+                {
+                    yraw[i*d+j] = xy[xew*i+2+j];
+                }
+            }
+            kxcur = (kx-1)/scalexy+1;
+            kycur = (ky-1)/scalexy+1;
+            
+            //
+            // Build initial dataset index; area is divided into (KXCur-1)*(KYCur-1)
+            // cells, with contiguous storage of points in the same cell.
+            // Iterate over different scales
+            //
+            alglib.smp.ae_shared_pool_set_seed(pool, seed);
+            reorderdatasetandbuildindex(xy, npoints, d, yraw, d, kxcur, kycur, ref xyindex, ref bufi, _params);
+            for(scaleidx=ntotallayers-1; scaleidx>=0; scaleidx--)
+            {
+                if( (nlayers>0 && scaleidx<nlayers) || (nlayers<=0 && scaleidx<apserv.imax2(ntotallayers+nlayers, 1, _params)) )
+                {
+                    
+                    //
+                    // Fit current layer
+                    //
+                    alglib.ap.assert(kxcur%basecasex==1, "Spline2DFit: integrity error");
+                    alglib.ap.assert(kycur%basecasey==1, "Spline2DFit: integrity error");
+                    tilescount0 = kxcur/basecasex;
+                    tilescount1 = kycur/basecasey;
+                    fastddmfitlayer(xy, d, scalexy, xyindex, basecasex, 0, tilescount0, tilescount0, basecasey, 0, tilescount1, tilescount1, maxcoresize, interfacesize, lsqrcnt, lambdaregfastddm+smoothing*Math.Pow(lambdadecay, scaleidx), basis1, pool, spline, _params);
+                    
+                    //
+                    // Compute residuals and update XY
+                    //
+                    computeresidualsfromscratch(xy, yraw, npoints, d, scalexy, spline, _params);
+                }
+                
+                //
+                // Move to the next level
+                //
+                if( scaleidx!=0 )
+                {
+                    
+                    //
+                    // Transform dataset (multply everything by 2.0) and refine grid.
+                    //
+                    kxcur = 2*kxcur-1;
+                    kycur = 2*kycur-1;
+                    scalexy = scalexy/2;
+                    invscalexy = (double)1/(double)scalexy;
+                    rescaledatasetandrefineindex(xy, npoints, d, yraw, d, kxcur, kycur, ref xyindex, ref bufi, _params);
+                    
+                    //
+                    // Clear temporaries from previous round.
+                    //
+                    // We have to do it because upper layer of the multilevel spline
+                    // needs more memory then subsequent layers, and we want to free
+                    // this memory as soon as possible.
+                    //
+                    alglib.smp.ae_shared_pool_clear_recycled(pool);
+                }
+            }
+            
+            //
+            // Post-check
+            //
+            alglib.ap.assert(kxcur==kx, "Spline2DFit: integrity check failed");
+            alglib.ap.assert(kycur==ky, "Spline2DFit: integrity check failed");
+            alglib.ap.assert(scalexy==1, "Spline2DFit: integrity check failed");
+            
+            //
+            // Report
+            //
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.maxerror = 0;
+            rss = 0.0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=d-1; j++)
+                {
+                    v = xy[i*xew+2+j];
+                    rss = rss+v*v;
+                    rep.rmserror = rep.rmserror+math.sqr(v);
+                    rep.avgerror = rep.avgerror+Math.Abs(v);
+                    rep.maxerror = Math.Max(rep.maxerror, Math.Abs(v));
+                }
+            }
+            rep.rmserror = Math.Sqrt(rep.rmserror/apserv.coalesce(npoints*d, 1.0, _params));
+            rep.avgerror = rep.avgerror/apserv.coalesce(npoints*d, 1.0, _params);
+            rep.r2 = 1.0-rss/apserv.coalesce(tss, 1.0, _params);
+        }
+
+
+        /*************************************************************************
+        Recursive fitting function for FastDDM algorithm.
+
+        Works with KX*KY grid, with KX=BasecaseX*TilesCountX+1 and KY=BasecaseY*TilesCountY+1,
+        which is partitioned into TilesCountX*TilesCountY tiles, each having size
+        BasecaseX*BasecaseY.
+
+        This function processes tiles in range [TileX0,TileX1)x[TileY0,TileY1) and
+        recursively divides this range until we move down to single tile, which
+        is processed with BlockLLS solver.
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void fastddmfitlayer(double[] xy,
+            int d,
+            int scalexy,
+            int[] xyindex,
+            int basecasex,
+            int tilex0,
+            int tilex1,
+            int tilescountx,
+            int basecasey,
+            int tiley0,
+            int tiley1,
+            int tilescounty,
+            int maxcoresize,
+            int interfacesize,
+            int lsqrcnt,
+            double lambdareg,
+            spline1d.spline1dinterpolant basis1,
+            alglib.smp.shared_pool pool,
+            spline2dinterpolant spline,
+            alglib.xparams _params)
+        {
+            int kx = 0;
+            int ky = 0;
+            int i = 0;
+            int j = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int bfrad = 0;
+            int xa = 0;
+            int xb = 0;
+            int ya = 0;
+            int yb = 0;
+            int tile0 = 0;
+            int tile1 = 0;
+            int tilesize0 = 0;
+            int tilesize1 = 0;
+            int sfx = 0;
+            int sfy = 0;
+            int sfxy = 0;
+            double dummytss = 0;
+            double invscalexy = 0;
+            int cnt0 = 0;
+            int cnt1 = 0;
+            int offs = 0;
+            double vs = 0;
+            double vsx = 0;
+            double vsy = 0;
+            double vsxy = 0;
+            spline2dfastddmbuf buf = null;
+
+            
+            //
+            // Dataset metrics and fast integrity checks;
+            // no code with side effects is allowed before parallel split.
+            //
+            bfrad = 2;
+            invscalexy = (double)1/(double)scalexy;
+            kx = basecasex*tilescountx+1;
+            ky = basecasey*tilescounty+1;
+            
+            //
+            // Parallelism; because this function is intended for
+            // large-scale problems, we always try to:
+            // * invoke parallel execution mode
+            // * activate spawn support
+            //
+            if( _trypexec_fastddmfitlayer(xy,d,scalexy,xyindex,basecasex,tilex0,tilex1,tilescountx,basecasey,tiley0,tiley1,tilescounty,maxcoresize,interfacesize,lsqrcnt,lambdareg,basis1,pool,spline, _params) )
+            {
+                return;
+            }
+            if( apserv.imax2(tiley1-tiley0, tilex1-tilex0, _params)>=2 )
+            {
+                if( tiley1-tiley0>tilex1-tilex0 )
+                {
+                    
+                    //
+                    // Split problem in Y dimension
+                    //
+                    // NOTE: recursive calls to FastDDMFitLayer() compute
+                    //       residuals in the inner cells defined by XYIndex[],
+                    //       but we still have to compute residuals for cells
+                    //       BETWEEN two recursive subdivisions of the task.
+                    //
+                    apserv.tiledsplit(tiley1-tiley0, 1, ref j0, ref j1, _params);
+                    fastddmfitlayer(xy, d, scalexy, xyindex, basecasex, tilex0, tilex1, tilescountx, basecasey, tiley0, tiley0+j0, tilescounty, maxcoresize, interfacesize, lsqrcnt, lambdareg, basis1, pool, spline, _params);
+                    fastddmfitlayer(xy, d, scalexy, xyindex, basecasex, tilex0, tilex1, tilescountx, basecasey, tiley0+j0, tiley1, tilescounty, maxcoresize, interfacesize, lsqrcnt, lambdareg, basis1, pool, spline, _params);
+                }
+                else
+                {
+                    
+                    //
+                    // Split problem in X dimension
+                    //
+                    // NOTE: recursive calls to FastDDMFitLayer() compute
+                    //       residuals in the inner cells defined by XYIndex[],
+                    //       but we still have to compute residuals for cells
+                    //       BETWEEN two recursive subdivisions of the task.
+                    //
+                    apserv.tiledsplit(tilex1-tilex0, 1, ref j0, ref j1, _params);
+                    fastddmfitlayer(xy, d, scalexy, xyindex, basecasex, tilex0, tilex0+j0, tilescountx, basecasey, tiley0, tiley1, tilescounty, maxcoresize, interfacesize, lsqrcnt, lambdareg, basis1, pool, spline, _params);
+                    fastddmfitlayer(xy, d, scalexy, xyindex, basecasex, tilex0+j0, tilex1, tilescountx, basecasey, tiley0, tiley1, tilescounty, maxcoresize, interfacesize, lsqrcnt, lambdareg, basis1, pool, spline, _params);
+                }
+                return;
+            }
+            alglib.ap.assert(tiley0==tiley1-1, "Spline2DFit.FastDDMFitLayer: integrity check failed");
+            alglib.ap.assert(tilex0==tilex1-1, "Spline2DFit.FastDDMFitLayer: integrity check failed");
+            tile1 = tiley0;
+            tile0 = tilex0;
+            
+            //
+            // Retrieve temporaries
+            //
+            alglib.smp.ae_shared_pool_retrieve(pool, ref buf);
+            
+            //
+            // Analyze dataset
+            //
+            xa = apserv.iboundval(tile0*basecasex-interfacesize, 0, kx, _params);
+            xb = apserv.iboundval((tile0+1)*basecasex+interfacesize, 0, kx, _params);
+            ya = apserv.iboundval(tile1*basecasey-interfacesize, 0, ky, _params);
+            yb = apserv.iboundval((tile1+1)*basecasey+interfacesize, 0, ky, _params);
+            tilesize0 = xb-xa;
+            tilesize1 = yb-ya;
+            
+            //
+            // Solve current chunk with BlockLLS
+            //
+            dummytss = 1.0;
+            xdesigngenerate(xy, xyindex, xa, xb, kx, ya, yb, ky, d, lambdareg, 0.0, basis1, buf.xdesignmatrix, _params);
+            blockllsfit(buf.xdesignmatrix, lsqrcnt, ref buf.tmpz, buf.dummyrep, dummytss, buf.blockllsbuf, _params);
+            buf.localmodel.d = d;
+            buf.localmodel.m = tilesize1;
+            buf.localmodel.n = tilesize0;
+            buf.localmodel.stype = -3;
+            apserv.rvectorsetlengthatleast(ref buf.localmodel.x, tilesize0, _params);
+            apserv.rvectorsetlengthatleast(ref buf.localmodel.y, tilesize1, _params);
+            apserv.rvectorsetlengthatleast(ref buf.localmodel.f, tilesize0*tilesize1*d*4, _params);
+            for(i=0; i<=tilesize0-1; i++)
+            {
+                buf.localmodel.x[i] = xa+i;
+            }
+            for(i=0; i<=tilesize1-1; i++)
+            {
+                buf.localmodel.y[i] = ya+i;
+            }
+            for(i=0; i<=tilesize0*tilesize1*d*4-1; i++)
+            {
+                buf.localmodel.f[i] = 0.0;
+            }
+            updatesplinetable(buf.tmpz, tilesize0, tilesize1, d, basis1, bfrad, buf.localmodel.f, tilesize1, tilesize0, 1, _params);
+            
+            //
+            // Transform local spline to original coordinates
+            //
+            sfx = buf.localmodel.n*buf.localmodel.m*d;
+            sfy = 2*buf.localmodel.n*buf.localmodel.m*d;
+            sfxy = 3*buf.localmodel.n*buf.localmodel.m*d;
+            for(i=0; i<=tilesize0-1; i++)
+            {
+                buf.localmodel.x[i] = buf.localmodel.x[i]*scalexy;
+            }
+            for(i=0; i<=tilesize1-1; i++)
+            {
+                buf.localmodel.y[i] = buf.localmodel.y[i]*scalexy;
+            }
+            for(i=0; i<=tilesize0*tilesize1*d-1; i++)
+            {
+                buf.localmodel.f[sfx+i] = buf.localmodel.f[sfx+i]*invscalexy;
+                buf.localmodel.f[sfy+i] = buf.localmodel.f[sfy+i]*invscalexy;
+                buf.localmodel.f[sfxy+i] = buf.localmodel.f[sfxy+i]*(invscalexy*invscalexy);
+            }
+            
+            //
+            // Output results; for inner and topmost/leftmost tiles we output only BasecaseX*BasecaseY
+            // inner elements; for rightmost/bottom ones we also output one column/row of the interface
+            // part.
+            //
+            // Such complexity is explained by the fact that area size (by design) is not evenly divisible
+            // by the tile size; it is divisible with remainder=1, and we expect that interface size is
+            // at least 1, so we can fill the missing rightmost/bottom elements of Z by the interface
+            // values.
+            //
+            alglib.ap.assert(interfacesize>=1, "Spline2DFit: integrity check failed");
+            sfx = spline.n*spline.m*d;
+            sfy = 2*spline.n*spline.m*d;
+            sfxy = 3*spline.n*spline.m*d;
+            cnt0 = basecasex*scalexy;
+            cnt1 = basecasey*scalexy;
+            if( tile0==tilescountx-1 )
+            {
+                apserv.inc(ref cnt0, _params);
+            }
+            if( tile1==tilescounty-1 )
+            {
+                apserv.inc(ref cnt1, _params);
+            }
+            offs = d*(spline.n*tile1*basecasey*scalexy+tile0*basecasex*scalexy);
+            for(j1=0; j1<=cnt1-1; j1++)
+            {
+                for(j0=0; j0<=cnt0-1; j0++)
+                {
+                    for(j=0; j<=d-1; j++)
+                    {
+                        spline2ddiffvi(buf.localmodel, tile0*basecasex*scalexy+j0, tile1*basecasey*scalexy+j1, j, ref vs, ref vsx, ref vsy, ref vsxy, _params);
+                        spline.f[offs+d*(spline.n*j1+j0)+j] = spline.f[offs+d*(spline.n*j1+j0)+j]+vs;
+                        spline.f[sfx+offs+d*(spline.n*j1+j0)+j] = spline.f[sfx+offs+d*(spline.n*j1+j0)+j]+vsx;
+                        spline.f[sfy+offs+d*(spline.n*j1+j0)+j] = spline.f[sfy+offs+d*(spline.n*j1+j0)+j]+vsy;
+                        spline.f[sfxy+offs+d*(spline.n*j1+j0)+j] = spline.f[sfxy+offs+d*(spline.n*j1+j0)+j]+vsxy;
+                    }
+                }
+            }
+            
+            //
+            // Recycle temporaries
+            //
+            alglib.smp.ae_shared_pool_recycle(pool, ref buf);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_fastddmfitlayer(double[] xy,
+            int d,
+            int scalexy,
+            int[] xyindex,
+            int basecasex,
+            int tilex0,
+            int tilex1,
+            int tilescountx,
+            int basecasey,
+            int tiley0,
+            int tiley1,
+            int tilescounty,
+            int maxcoresize,
+            int interfacesize,
+            int lsqrcnt,
+            double lambdareg,
+            spline1d.spline1dinterpolant basis1,
+            alglib.smp.shared_pool pool,
+            spline2dinterpolant spline, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        This function performs fitting with  BlockLLS solver.  Internal  function,
+        never use it directly.
+
+        IMPORTANT: performance  and  memory  requirements  of  this  function  are
+                   asymmetric w.r.t. KX and KY: it has
+                   * O(KY*KX^2) memory requirements
+                   * O(KY*KX^3) running time
+                   Thus, if you have large KY and small KX,  simple  transposition
+                   of your dataset may give you great speedup.
+
+        INPUT PARAMETERS:
+            AV      -   sparse matrix, [ARows,KX*KY] in size.  "Vertical"  version
+                        of design matrix, rows [0,NPoints) contain values of basis
+                        functions at dataset  points.  Other  rows  are  used  for
+                        nonlinearity penalty and other stuff like that.
+            AH      -   transpose(AV), "horizontal" version of AV
+            ARows   -   rows count
+            XY      -   array[NPoints*(2+D)], dataset
+            KX, KY  -   grid size
+            NPoints -   points count
+            D       -   number of components in vector-valued spline, D>=1
+            LSQRCnt -   number of iterations, non-zero:
+                        * LSQRCnt>0 means that specified amount of  preconditioned
+                          LSQR  iterations  will  be  performed  to solve problem;
+                          usually  we  need  2..5  its.  Recommended option - best
+                          convergence and stability/quality.
+                        * LSQRCnt<0 means that instead of LSQR  we  use  iterative
+                          refinement on normal equations. Again, 2..5 its is enough.
+            Z       -   possibly preallocated buffer for solution
+            Rep     -   report structure; fields which are not set by this function
+                        are left intact
+            TSS     -   total sum of squares; used to calculate R2
+            
+
+        OUTPUT PARAMETERS:
+            XY      -   destroyed in process
+            Z       -   array[KX*KY*D], filled by solution; KX*KY coefficients
+                        corresponding to each of D dimensions are stored contiguously.
+            Rep         -   following fields are set:
+                            * Rep.RMSError
+                            * Rep.AvgError
+                            * Rep.MaxError
+                            * Rep.R2
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void blockllsfit(spline2dxdesignmatrix xdesign,
+            int lsqrcnt,
+            ref double[] z,
+            spline2dfitreport rep,
+            double tss,
+            spline2dblockllsbuf buf,
+            alglib.xparams _params)
+        {
+            int blockbandwidth = 0;
+            int d = 0;
+            int i = 0;
+            int j = 0;
+            double lambdachol = 0;
+            apserv.sreal mxata = new apserv.sreal();
+            double v = 0;
+            int celloffset = 0;
+            int i0 = 0;
+            int i1 = 0;
+            double rss = 0;
+            int arows = 0;
+            int bw2 = 0;
+            int kx = 0;
+            int ky = 0;
+
+            alglib.ap.assert(xdesign.blockwidth==4, "Spline2DFit: integrity check failed");
+            blockbandwidth = 3;
+            d = xdesign.d;
+            arows = xdesign.nrows;
+            kx = xdesign.kx;
+            ky = xdesign.ky;
+            bw2 = xdesign.blockwidth*xdesign.blockwidth;
+            
+            //
+            // Initial values for Z/Residuals
+            //
+            apserv.rvectorsetlengthatleast(ref z, kx*ky*d, _params);
+            for(i=0; i<=kx*ky*d-1; i++)
+            {
+                z[i] = 0;
+            }
+            
+            //
+            // Create and factorize design matrix. Add regularizer if
+            // factorization failed (happens sometimes with zero
+            // smoothing and sparsely populated datasets).
+            //
+            // The algorithm below is refactoring of NaiveLLS algorithm,
+            // which uses sparsity properties and compressed block storage.
+            //
+            // Problem sparsity pattern results in block-band-diagonal
+            // matrix (block matrix with limited bandwidth, equal to 3
+            // for bicubic splines). Thus, we have KY*KY blocks, each
+            // of them is KX*KX in size. Design matrix is stored in
+            // large NROWS*KX matrix, with NROWS=(BlockBandwidth+1)*KY*KX.
+            //
+            // We use adaptation of block skyline storage format, with
+            // TOWERSIZE*KX skyline bands (towers) stored sequentially;
+            // here TOWERSIZE=(BlockBandwidth+1)*KX. So, we have KY
+            // "towers", stored one below other, in BlockATA matrix.
+            // Every "tower" is a sequence of BlockBandwidth+1 cells,
+            // each of them being KX*KX in size.
+            //
+            lambdachol = cholreg;
+            apserv.rmatrixsetlengthatleast(ref buf.blockata, (blockbandwidth+1)*ky*kx, kx, _params);
+            while( true )
+            {
+                
+                //
+                // Parallel generation of squared design matrix.
+                //
+                xdesignblockata(xdesign, buf.blockata, ref mxata.val, _params);
+                
+                //
+                // Regularization
+                //
+                v = apserv.coalesce(mxata.val, 1.0, _params)*lambdachol;
+                for(i1=0; i1<=ky-1; i1++)
+                {
+                    celloffset = getcelloffset(kx, ky, blockbandwidth, i1, i1, _params);
+                    for(i0=0; i0<=kx-1; i0++)
+                    {
+                        buf.blockata[celloffset+i0,i0] = buf.blockata[celloffset+i0,i0]+v;
+                    }
+                }
+                
+                //
+                // Try Cholesky factorization.
+                //
+                if( !blockllscholesky(buf.blockata, kx, ky, ref buf.trsmbuf2, ref buf.cholbuf2, ref buf.cholbuf1, _params) )
+                {
+                    
+                    //
+                    // Factorization failed, increase regularizer and repeat
+                    //
+                    lambdachol = apserv.coalesce(10*lambdachol, 1.0E-12, _params);
+                    continue;
+                }
+                break;
+            }
+            
+            //
+            // Solve
+            //
+            rss = 0.0;
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.maxerror = 0;
+            alglib.ap.assert(lsqrcnt>0, "Spline2DFit: integrity failure");
+            apserv.rvectorsetlengthatleast(ref buf.tmp0, arows, _params);
+            apserv.rvectorsetlengthatleast(ref buf.tmp1, kx*ky, _params);
+            linlsqr.linlsqrcreatebuf(arows, kx*ky, buf.solver, _params);
+            for(j=0; j<=d-1; j++)
+            {
+                
+                //
+                // Preconditioned LSQR:
+                //
+                // use Cholesky factor U of squared design matrix A'*A to
+                // transform min|A*x-b| to min|[A*inv(U)]*y-b| with y=U*x.
+                //
+                // Preconditioned problem is solved with LSQR solver, which
+                // gives superior results than normal equations.
+                //
+                for(i=0; i<=arows-1; i++)
+                {
+                    if( i<xdesign.npoints )
+                    {
+                        buf.tmp0[i] = xdesign.vals[i,bw2+j];
+                    }
+                    else
+                    {
+                        buf.tmp0[i] = 0.0;
+                    }
+                }
+                linlsqr.linlsqrrestart(buf.solver, _params);
+                linlsqr.linlsqrsetb(buf.solver, buf.tmp0, _params);
+                linlsqr.linlsqrsetcond(buf.solver, 1.0E-14, 1.0E-14, lsqrcnt, _params);
+                while( linlsqr.linlsqriteration(buf.solver, _params) )
+                {
+                    if( buf.solver.needmv )
+                    {
+                        
+                        //
+                        // Use Cholesky factorization of the system matrix
+                        // as preconditioner: solve TRSV(U,Solver.X)
+                        //
+                        for(i=0; i<=kx*ky-1; i++)
+                        {
+                            buf.tmp1[i] = buf.solver.x[i];
+                        }
+                        blockllstrsv(buf.blockata, kx, ky, false, buf.tmp1, _params);
+                        
+                        //
+                        // After preconditioning is done, multiply by A
+                        //
+                        xdesignmv(xdesign, buf.tmp1, ref buf.solver.mv, _params);
+                    }
+                    if( buf.solver.needmtv )
+                    {
+                        
+                        //
+                        // Multiply by design matrix A
+                        //
+                        xdesignmtv(xdesign, buf.solver.x, ref buf.solver.mtv, _params);
+                        
+                        //
+                        // Multiply by preconditioner: solve TRSV(U',A*Solver.X)
+                        //
+                        blockllstrsv(buf.blockata, kx, ky, true, buf.solver.mtv, _params);
+                    }
+                }
+                
+                //
+                // Get results and post-multiply by preconditioner to get
+                // original variables.
+                //
+                linlsqr.linlsqrresults(buf.solver, ref buf.tmp1, buf.solverrep, _params);
+                blockllstrsv(buf.blockata, kx, ky, false, buf.tmp1, _params);
+                for(i=0; i<=kx*ky-1; i++)
+                {
+                    z[kx*ky*j+i] = buf.tmp1[i];
+                }
+                
+                //
+                // Calculate model values
+                //
+                xdesignmv(xdesign, buf.tmp1, ref buf.tmp0, _params);
+                for(i=0; i<=xdesign.npoints-1; i++)
+                {
+                    v = xdesign.vals[i,bw2+j]-buf.tmp0[i];
+                    rss = rss+v*v;
+                    rep.rmserror = rep.rmserror+math.sqr(v);
+                    rep.avgerror = rep.avgerror+Math.Abs(v);
+                    rep.maxerror = Math.Max(rep.maxerror, Math.Abs(v));
+                }
+            }
+            rep.rmserror = Math.Sqrt(rep.rmserror/apserv.coalesce(xdesign.npoints*d, 1.0, _params));
+            rep.avgerror = rep.avgerror/apserv.coalesce(xdesign.npoints*d, 1.0, _params);
+            rep.r2 = 1.0-rss/apserv.coalesce(tss, 1.0, _params);
+        }
+
+
+        /*************************************************************************
+        This function performs fitting with  NaiveLLS solver.  Internal  function,
+        never use it directly.
+
+        INPUT PARAMETERS:
+            AV      -   sparse matrix, [ARows,KX*KY] in size.  "Vertical"  version
+                        of design matrix, rows [0,NPoints] contain values of basis
+                        functions at dataset  points.  Other  rows  are  used  for
+                        nonlinearity penalty and other stuff like that.
+            AH      -   transpose(AV), "horizontal" version of AV
+            ARows   -   rows count
+            XY      -   array[NPoints*(2+D)], dataset
+            KX, KY  -   grid size
+            NPoints -   points count
+            D       -   number of components in vector-valued spline, D>=1
+            LSQRCnt -   number of iterations, non-zero:
+                        * LSQRCnt>0 means that specified amount of  preconditioned
+                          LSQR  iterations  will  be  performed  to solve problem;
+                          usually  we  need  2..5  its.  Recommended option - best
+                          convergence and stability/quality.
+                        * LSQRCnt<0 means that instead of LSQR  we  use  iterative
+                          refinement on normal equations. Again, 2..5 its is enough.
+            Z       -   possibly preallocated buffer for solution
+            Rep     -   report structure; fields which are not set by this function
+                        are left intact
+            TSS     -   total sum of squares; used to calculate R2
+            
+
+        OUTPUT PARAMETERS:
+            XY      -   destroyed in process
+            Z       -   array[KX*KY*D], filled by solution; KX*KY coefficients
+                        corresponding to each of D dimensions are stored contiguously.
+            Rep     -   following fields are set:
+                            * Rep.RMSError
+                            * Rep.AvgError
+                            * Rep.MaxError
+                            * Rep.R2
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void naivellsfit(sparse.sparsematrix av,
+            sparse.sparsematrix ah,
+            int arows,
+            double[] xy,
+            int kx,
+            int ky,
+            int npoints,
+            int d,
+            int lsqrcnt,
+            ref double[] z,
+            spline2dfitreport rep,
+            double tss,
+            alglib.xparams _params)
+        {
+            int ew = 0;
+            int i = 0;
+            int j = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int j0 = 0;
+            int j1 = 0;
+            double v = 0;
+            int blockbandwidth = 0;
+            double lambdareg = 0;
+            int srci = 0;
+            int srcj = 0;
+            int idxi = 0;
+            int idxj = 0;
+            int endi = 0;
+            int endj = 0;
+            int rfsidx = 0;
+            double[,] ata = new double[0,0];
+            double[] tmp0 = new double[0];
+            double[] tmp1 = new double[0];
+            double mxata = 0;
+            linlsqr.linlsqrstate solver = new linlsqr.linlsqrstate();
+            linlsqr.linlsqrreport solverrep = new linlsqr.linlsqrreport();
+            double rss = 0;
+
+            blockbandwidth = 3;
+            ew = 2+d;
+            
+            //
+            // Initial values for Z/Residuals
+            //
+            apserv.rvectorsetlengthatleast(ref z, kx*ky*d, _params);
+            for(i=0; i<=kx*ky*d-1; i++)
+            {
+                z[i] = 0;
+            }
+            
+            //
+            // Create and factorize design matrix.
+            //
+            // Add regularizer if factorization failed (happens sometimes
+            // with zero smoothing and sparsely populated datasets).
+            //
+            lambdareg = cholreg;
+            apserv.rmatrixsetlengthatleast(ref ata, kx*ky, kx*ky, _params);
+            while( true )
+            {
+                mxata = 0.0;
+                for(i=0; i<=kx*ky-1; i++)
+                {
+                    for(j=i; j<=kx*ky-1; j++)
+                    {
+                        
+                        //
+                        // Initialize by zero
+                        //
+                        ata[i,j] = 0;
+                        
+                        //
+                        // Determine grid nodes corresponding to I and J;
+                        // skip if too far away
+                        //
+                        i0 = i%kx;
+                        i1 = i/kx;
+                        j0 = j%kx;
+                        j1 = j/kx;
+                        if( Math.Abs(i0-j0)>blockbandwidth || Math.Abs(i1-j1)>blockbandwidth )
+                        {
+                            continue;
+                        }
+                        
+                        //
+                        // Nodes are close enough, calculate product of columns I and J of A.
+                        //
+                        v = 0;
+                        srci = ah.ridx[i];
+                        srcj = ah.ridx[j];
+                        endi = ah.ridx[i+1];
+                        endj = ah.ridx[j+1];
+                        while( true )
+                        {
+                            if( srci>=endi || srcj>=endj )
+                            {
+                                break;
+                            }
+                            idxi = ah.idx[srci];
+                            idxj = ah.idx[srcj];
+                            if( idxi==idxj )
+                            {
+                                v = v+ah.vals[srci]*ah.vals[srcj];
+                                srci = srci+1;
+                                srcj = srcj+1;
+                                continue;
+                            }
+                            if( idxi<idxj )
+                            {
+                                srci = srci+1;
+                            }
+                            else
+                            {
+                                srcj = srcj+1;
+                            }
+                        }
+                        ata[i,j] = v;
+                        mxata = Math.Max(mxata, Math.Abs(v));
+                    }
+                }
+                v = apserv.coalesce(mxata, 1.0, _params)*lambdareg;
+                for(i=0; i<=kx*ky-1; i++)
+                {
+                    ata[i,i] = ata[i,i]+v;
+                }
+                if( trfac.spdmatrixcholesky(ref ata, kx*ky, true, _params) )
+                {
+                    
+                    //
+                    // Success!
+                    //
+                    break;
+                }
+                
+                //
+                // Factorization failed, increase regularizer and repeat
+                //
+                lambdareg = apserv.coalesce(10*lambdareg, 1.0E-12, _params);
+            }
+            
+            //
+            // Solve
+            //
+            // NOTE: we expect that Z is zero-filled, and we treat it
+            //       like initial approximation to solution.
+            //
+            apserv.rvectorsetlengthatleast(ref tmp0, arows, _params);
+            apserv.rvectorsetlengthatleast(ref tmp1, kx*ky, _params);
+            if( lsqrcnt>0 )
+            {
+                linlsqr.linlsqrcreate(arows, kx*ky, solver, _params);
+            }
+            for(j=0; j<=d-1; j++)
+            {
+                alglib.ap.assert(lsqrcnt!=0, "Spline2DFit: integrity failure");
+                if( lsqrcnt>0 )
+                {
+                    
+                    //
+                    // Preconditioned LSQR:
+                    //
+                    // use Cholesky factor U of squared design matrix A'*A to
+                    // transform min|A*x-b| to min|[A*inv(U)]*y-b| with y=U*x.
+                    //
+                    // Preconditioned problem is solved with LSQR solver, which
+                    // gives superior results than normal equations.
+                    //
+                    linlsqr.linlsqrcreate(arows, kx*ky, solver, _params);
+                    for(i=0; i<=arows-1; i++)
+                    {
+                        if( i<npoints )
+                        {
+                            tmp0[i] = xy[i*ew+2+j];
+                        }
+                        else
+                        {
+                            tmp0[i] = 0.0;
+                        }
+                    }
+                    linlsqr.linlsqrsetb(solver, tmp0, _params);
+                    linlsqr.linlsqrsetcond(solver, 1.0E-14, 1.0E-14, lsqrcnt, _params);
+                    while( linlsqr.linlsqriteration(solver, _params) )
+                    {
+                        if( solver.needmv )
+                        {
+                            
+                            //
+                            // Use Cholesky factorization of the system matrix
+                            // as preconditioner: solve TRSV(U,Solver.X)
+                            //
+                            for(i=0; i<=kx*ky-1; i++)
+                            {
+                                tmp1[i] = solver.x[i];
+                            }
+                            ablas.rmatrixtrsv(kx*ky, ata, 0, 0, true, false, 0, tmp1, 0, _params);
+                            
+                            //
+                            // After preconditioning is done, multiply by A
+                            //
+                            sparse.sparsemv(av, tmp1, ref solver.mv, _params);
+                        }
+                        if( solver.needmtv )
+                        {
+                            
+                            //
+                            // Multiply by design matrix A
+                            //
+                            sparse.sparsemv(ah, solver.x, ref solver.mtv, _params);
+                            
+                            //
+                            // Multiply by preconditioner: solve TRSV(U',A*Solver.X)
+                            //
+                            ablas.rmatrixtrsv(kx*ky, ata, 0, 0, true, false, 1, solver.mtv, 0, _params);
+                        }
+                    }
+                    linlsqr.linlsqrresults(solver, ref tmp1, solverrep, _params);
+                    ablas.rmatrixtrsv(kx*ky, ata, 0, 0, true, false, 0, tmp1, 0, _params);
+                    for(i=0; i<=kx*ky-1; i++)
+                    {
+                        z[kx*ky*j+i] = tmp1[i];
+                    }
+                    
+                    //
+                    // Calculate model values
+                    //
+                    sparse.sparsemv(av, tmp1, ref tmp0, _params);
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        xy[i*ew+2+j] = xy[i*ew+2+j]-tmp0[i];
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Iterative refinement, inferior to LSQR
+                    //
+                    // For each dimension D:
+                    // * fetch current estimate for solution from Z to Tmp1
+                    // * calculate residual r for current estimate, store in Tmp0
+                    // * calculate product of residual and design matrix A'*r, store it in Tmp1
+                    // * Cholesky solver
+                    // * update current estimate
+                    //
+                    for(rfsidx=1; rfsidx<=-lsqrcnt; rfsidx++)
+                    {
+                        for(i=0; i<=kx*ky-1; i++)
+                        {
+                            tmp1[i] = z[kx*ky*j+i];
+                        }
+                        sparse.sparsemv(av, tmp1, ref tmp0, _params);
+                        for(i=0; i<=arows-1; i++)
+                        {
+                            if( i<npoints )
+                            {
+                                v = xy[i*ew+2+j];
+                            }
+                            else
+                            {
+                                v = 0;
+                            }
+                            tmp0[i] = v-tmp0[i];
+                        }
+                        sparse.sparsemv(ah, tmp0, ref tmp1, _params);
+                        ablas.rmatrixtrsv(kx*ky, ata, 0, 0, true, false, 1, tmp1, 0, _params);
+                        ablas.rmatrixtrsv(kx*ky, ata, 0, 0, true, false, 0, tmp1, 0, _params);
+                        for(i=0; i<=kx*ky-1; i++)
+                        {
+                            z[kx*ky*j+i] = z[kx*ky*j+i]+tmp1[i];
+                        }
+                    }
+                    
+                    //
+                    // Calculate model values
+                    //
+                    for(i=0; i<=kx*ky-1; i++)
+                    {
+                        tmp1[i] = z[kx*ky*j+i];
+                    }
+                    sparse.sparsemv(av, tmp1, ref tmp0, _params);
+                    for(i=0; i<=npoints-1; i++)
+                    {
+                        xy[i*ew+2+j] = xy[i*ew+2+j]-tmp0[i];
+                    }
+                }
+            }
+            
+            //
+            // Generate report
+            //
+            rep.rmserror = 0;
+            rep.avgerror = 0;
+            rep.maxerror = 0;
+            rss = 0.0;
+            for(i=0; i<=npoints-1; i++)
+            {
+                for(j=0; j<=d-1; j++)
+                {
+                    v = xy[i*ew+2+j];
+                    rss = rss+v*v;
+                    rep.rmserror = rep.rmserror+math.sqr(v);
+                    rep.avgerror = rep.avgerror+Math.Abs(v);
+                    rep.maxerror = Math.Max(rep.maxerror, Math.Abs(v));
+                }
+            }
+            rep.rmserror = Math.Sqrt(rep.rmserror/apserv.coalesce(npoints*d, 1.0, _params));
+            rep.avgerror = rep.avgerror/apserv.coalesce(npoints*d, 1.0, _params);
+            rep.r2 = 1.0-rss/apserv.coalesce(tss, 1.0, _params);
+        }
+
+
+        /*************************************************************************
+        This  is  convenience  function  for band block storage format; it returns
+        offset of KX*KX-sized block (I,J) in a compressed 2D array.
+
+        For specific offset=OFFSET,
+        block (I,J) will be stored in entries BlockMatrix[OFFSET:OFFSET+KX-1,0:KX-1]
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static int getcelloffset(int kx,
+            int ky,
+            int blockbandwidth,
+            int i,
+            int j,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            alglib.ap.assert(i>=0 && i<ky, "Spline2DFit: GetCellOffset() integrity error");
+            alglib.ap.assert(j>=0 && j<ky, "Spline2DFit: GetCellOffset() integrity error");
+            alglib.ap.assert(j>=i && j<=i+blockbandwidth, "Spline2DFit: GetCellOffset() integrity error");
+            result = j*(blockbandwidth+1)*kx;
+            result = result+(blockbandwidth-(j-i))*kx;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This  is  convenience  function  for band block storage format; it  copies
+        cell (I,J) from compressed format to uncompressed general matrix, at desired
+        position.
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void copycellto(int kx,
+            int ky,
+            int blockbandwidth,
+            double[,] blockata,
+            int i,
+            int j,
+            double[,] dst,
+            int dst0,
+            int dst1,
+            alglib.xparams _params)
+        {
+            int celloffset = 0;
+            int idx0 = 0;
+            int idx1 = 0;
+
+            celloffset = getcelloffset(kx, ky, blockbandwidth, i, j, _params);
+            for(idx0=0; idx0<=kx-1; idx0++)
+            {
+                for(idx1=0; idx1<=kx-1; idx1++)
+                {
+                    dst[dst0+idx0,dst1+idx1] = blockata[celloffset+idx0,idx1];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This  is  convenience  function  for band block storage format; it
+        truncates all elements of  cell (I,J) which are less than Eps in magnitude.
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void flushtozerocell(int kx,
+            int ky,
+            int blockbandwidth,
+            double[,] blockata,
+            int i,
+            int j,
+            double eps,
+            alglib.xparams _params)
+        {
+            int celloffset = 0;
+            int idx0 = 0;
+            int idx1 = 0;
+            double eps2 = 0;
+            double v = 0;
+
+            celloffset = getcelloffset(kx, ky, blockbandwidth, i, j, _params);
+            eps2 = eps*eps;
+            for(idx0=0; idx0<=kx-1; idx0++)
+            {
+                for(idx1=0; idx1<=kx-1; idx1++)
+                {
+                    v = blockata[celloffset+idx0,idx1];
+                    if( v*v<eps2 )
+                    {
+                        blockata[celloffset+idx0,idx1] = 0;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function generates squared design matrix stored in block band format.
+
+        We use adaptation of block skyline storage format, with
+        TOWERSIZE*KX skyline bands (towers) stored sequentially;
+        here TOWERSIZE=(BlockBandwidth+1)*KX. So, we have KY
+        "towers", stored one below other, in BlockATA matrix.
+        Every "tower" is a sequence of BlockBandwidth+1 cells,
+        each of them being KX*KX in size.
+
+        INPUT PARAMETERS:
+            AH      -   sparse matrix, [KX*KY,ARows] in size. "Horizontal" version
+                        of design matrix, cols [0,NPoints] contain values of basis
+                        functions at dataset  points.  Other  cols  are  used  for
+                        nonlinearity penalty and other stuff like that.
+            KY0, KY1-   subset of output matrix bands to process; on entry it MUST
+                        be set to 0 and KY respectively.
+            KX, KY  -   grid size
+            BlockATA-   array[KY*(BlockBandwidth+1)*KX,KX],  preallocated  storage
+                        for output matrix in compressed block band format
+            MXATA   -   on entry MUST be zero
+
+        OUTPUT PARAMETERS:
+            BlockATA-   AH*AH', stored in compressed block band format
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void blockllsgenerateata(sparse.sparsematrix ah,
+            int ky0,
+            int ky1,
+            int kx,
+            int ky,
+            double[,] blockata,
+            apserv.sreal mxata,
+            alglib.xparams _params)
+        {
+            int blockbandwidth = 0;
+            double avgrowlen = 0;
+            double cellcost = 0;
+            double totalcost = 0;
+            apserv.sreal tmpmxata = new apserv.sreal();
+            int i = 0;
+            int j = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int celloffset = 0;
+            double v = 0;
+            int srci = 0;
+            int srcj = 0;
+            int idxi = 0;
+            int idxj = 0;
+            int endi = 0;
+            int endj = 0;
+
+            alglib.ap.assert((double)(mxata.val)>=(double)(0), "BlockLLSGenerateATA: integrity check failed");
+            blockbandwidth = 3;
+            
+            //
+            // Determine problem cost, perform recursive subdivision
+            // (with optional parallelization)
+            //
+            avgrowlen = (double)ah.ridx[kx*ky]/(double)(kx*ky);
+            cellcost = apserv.rmul3(kx, 1+2*blockbandwidth, avgrowlen, _params);
+            totalcost = apserv.rmul3(ky1-ky0, 1+2*blockbandwidth, cellcost, _params);
+            if( ky1-ky0>=2 && (double)(totalcost)>(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_blockllsgenerateata(ah,ky0,ky1,kx,ky,blockata,mxata, _params) )
+                {
+                    return;
+                }
+            }
+            if( ky1-ky0>=2 )
+            {
+                
+                //
+                // Split X: X*A = (X1 X2)^T*A
+                //
+                j = (ky1-ky0)/2;
+                blockllsgenerateata(ah, ky0, ky0+j, kx, ky, blockata, tmpmxata, _params);
+                blockllsgenerateata(ah, ky0+j, ky1, kx, ky, blockata, mxata, _params);
+                mxata.val = Math.Max(mxata.val, tmpmxata.val);
+                return;
+            }
+            
+            //
+            // Splitting in Y-dimension is done, fill I1-th "tower"
+            //
+            alglib.ap.assert(ky1==ky0+1, "BlockLLSGenerateATA: integrity check failed");
+            i1 = ky0;
+            for(j1=i1; j1<=Math.Min(ky-1, i1+blockbandwidth); j1++)
+            {
+                celloffset = getcelloffset(kx, ky, blockbandwidth, i1, j1, _params);
+                
+                //
+                // Clear cell (I1,J1)
+                //
+                for(i0=0; i0<=kx-1; i0++)
+                {
+                    for(j0=0; j0<=kx-1; j0++)
+                    {
+                        blockata[celloffset+i0,j0] = 0.0;
+                    }
+                }
+                
+                //
+                // Initialize cell internals
+                //
+                for(i0=0; i0<=kx-1; i0++)
+                {
+                    for(j0=0; j0<=kx-1; j0++)
+                    {
+                        if( Math.Abs(i0-j0)<=blockbandwidth )
+                        {
+                            
+                            //
+                            // Nodes are close enough, calculate product of columns I and J of A.
+                            //
+                            v = 0;
+                            i = i1*kx+i0;
+                            j = j1*kx+j0;
+                            srci = ah.ridx[i];
+                            srcj = ah.ridx[j];
+                            endi = ah.ridx[i+1];
+                            endj = ah.ridx[j+1];
+                            while( true )
+                            {
+                                if( srci>=endi || srcj>=endj )
+                                {
+                                    break;
+                                }
+                                idxi = ah.idx[srci];
+                                idxj = ah.idx[srcj];
+                                if( idxi==idxj )
+                                {
+                                    v = v+ah.vals[srci]*ah.vals[srcj];
+                                    srci = srci+1;
+                                    srcj = srcj+1;
+                                    continue;
+                                }
+                                if( idxi<idxj )
+                                {
+                                    srci = srci+1;
+                                }
+                                else
+                                {
+                                    srcj = srcj+1;
+                                }
+                            }
+                            blockata[celloffset+i0,j0] = v;
+                            mxata.val = Math.Max(mxata.val, Math.Abs(v));
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_blockllsgenerateata(sparse.sparsematrix ah,
+            int ky0,
+            int ky1,
+            int kx,
+            int ky,
+            double[,] blockata,
+            apserv.sreal mxata, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        This function performs Cholesky decomposition of squared design matrix
+        stored in block band format.
+
+        INPUT PARAMETERS:
+            BlockATA        -   array[KY*(BlockBandwidth+1)*KX,KX], matrix in compressed
+                                block band format
+            KX, KY          -   grid size
+            TrsmBuf2,
+            CholBuf2,
+            CholBuf1        -   buffers; reused by this function on subsequent calls,
+                                automatically preallocated on the first call
+
+        OUTPUT PARAMETERS:
+            BlockATA-   Cholesky factor, in compressed block band format
+
+        Result:
+            True on success, False on Cholesky failure
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool blockllscholesky(double[,] blockata,
+            int kx,
+            int ky,
+            ref double[,] trsmbuf2,
+            ref double[,] cholbuf2,
+            ref double[] cholbuf1,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int blockbandwidth = 0;
+            int blockidx = 0;
+            int i = 0;
+            int j = 0;
+            int celloffset = 0;
+            int celloffset1 = 0;
+
+            blockbandwidth = 3;
+            apserv.rmatrixsetlengthatleast(ref trsmbuf2, (blockbandwidth+1)*kx, (blockbandwidth+1)*kx, _params);
+            apserv.rmatrixsetlengthatleast(ref cholbuf2, kx, kx, _params);
+            apserv.rvectorsetlengthatleast(ref cholbuf1, kx, _params);
+            result = true;
+            for(blockidx=0; blockidx<=ky-1; blockidx++)
+            {
+                
+                //
+                // TRSM for TRAIL*TRAIL block matrix before current cell;
+                // here TRAIL=MinInt(BlockIdx,BlockBandwidth).
+                //
+                for(i=0; i<=Math.Min(blockidx, blockbandwidth)-1; i++)
+                {
+                    for(j=i; j<=Math.Min(blockidx, blockbandwidth)-1; j++)
+                    {
+                        copycellto(kx, ky, blockbandwidth, blockata, Math.Max(blockidx-blockbandwidth, 0)+i, Math.Max(blockidx-blockbandwidth, 0)+j, trsmbuf2, i*kx, j*kx, _params);
+                    }
+                }
+                celloffset = getcelloffset(kx, ky, blockbandwidth, Math.Max(blockidx-blockbandwidth, 0), blockidx, _params);
+                ablas.rmatrixlefttrsm(Math.Min(blockidx, blockbandwidth)*kx, kx, trsmbuf2, 0, 0, true, false, 1, blockata, celloffset, 0, _params);
+                
+                //
+                // SYRK for diagonal cell: MaxInt(BlockIdx-BlockBandwidth,0)
+                // cells above diagonal one are used for update.
+                //
+                celloffset = getcelloffset(kx, ky, blockbandwidth, Math.Max(blockidx-blockbandwidth, 0), blockidx, _params);
+                celloffset1 = getcelloffset(kx, ky, blockbandwidth, blockidx, blockidx, _params);
+                ablas.rmatrixsyrk(kx, Math.Min(blockidx, blockbandwidth)*kx, -1.0, blockata, celloffset, 0, 1, 1.0, blockata, celloffset1, 0, true, _params);
+                
+                //
+                // Factorize diagonal cell
+                //
+                celloffset = getcelloffset(kx, ky, blockbandwidth, blockidx, blockidx, _params);
+                ablas.rmatrixcopy(kx, kx, blockata, celloffset, 0, cholbuf2, 0, 0, _params);
+                if( !trfac.spdmatrixcholeskyrec(ref cholbuf2, 0, kx, true, ref cholbuf1, _params) )
+                {
+                    result = false;
+                    return result;
+                }
+                ablas.rmatrixcopy(kx, kx, cholbuf2, 0, 0, blockata, celloffset, 0, _params);
+                
+                //
+                // PERFORMANCE TWEAK: drop nearly-denormals from last "tower".
+                //
+                // Sparse matrices like these may produce denormal numbers on
+                // sparse datasets, with significant (10x!) performance penalty
+                // on Intel chips. In order to avoid it, we manually truncate
+                // small enough numbers.
+                //
+                // We use 1.0E-50 as clipping level (not really denormal, but
+                // such small numbers are not actually important anyway).
+                //
+                for(i=Math.Max(blockidx-blockbandwidth, 0); i<=blockidx; i++)
+                {
+                    flushtozerocell(kx, ky, blockbandwidth, blockata, i, blockidx, 1.0E-50, _params);
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function performs TRSV on upper triangular Cholesky factor U, solving
+        either U*x=b or U'*x=b.
+
+        INPUT PARAMETERS:
+            BlockATA        -   array[KY*(BlockBandwidth+1)*KX,KX], matrix U
+                                in compressed block band format
+            KX, KY          -   grid size
+            TransU          -   whether to transpose U or not
+            B               -   array[KX*KY], on entry - stores right part B
+
+        OUTPUT PARAMETERS:
+            B               -   replaced by X
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void blockllstrsv(double[,] blockata,
+            int kx,
+            int ky,
+            bool transu,
+            double[] b,
+            alglib.xparams _params)
+        {
+            int blockbandwidth = 0;
+            int blockidx = 0;
+            int blockidx1 = 0;
+            int celloffset = 0;
+
+            blockbandwidth = 3;
+            if( !transu )
+            {
+                
+                //
+                // Solve U*x=b
+                //
+                for(blockidx=ky-1; blockidx>=0; blockidx--)
+                {
+                    for(blockidx1=1; blockidx1<=Math.Min(ky-(blockidx+1), blockbandwidth); blockidx1++)
+                    {
+                        celloffset = getcelloffset(kx, ky, blockbandwidth, blockidx, blockidx+blockidx1, _params);
+                        ablas.rmatrixgemv(kx, kx, -1.0, blockata, celloffset, 0, 0, b, (blockidx+blockidx1)*kx, 1.0, b, blockidx*kx, _params);
+                    }
+                    celloffset = getcelloffset(kx, ky, blockbandwidth, blockidx, blockidx, _params);
+                    ablas.rmatrixtrsv(kx, blockata, celloffset, 0, true, false, 0, b, blockidx*kx, _params);
+                }
+            }
+            else
+            {
+                
+                //
+                // Solve U'*x=b
+                //
+                for(blockidx=0; blockidx<=ky-1; blockidx++)
+                {
+                    celloffset = getcelloffset(kx, ky, blockbandwidth, blockidx, blockidx, _params);
+                    ablas.rmatrixtrsv(kx, blockata, celloffset, 0, true, false, 1, b, blockidx*kx, _params);
+                    for(blockidx1=1; blockidx1<=Math.Min(ky-(blockidx+1), blockbandwidth); blockidx1++)
+                    {
+                        celloffset = getcelloffset(kx, ky, blockbandwidth, blockidx, blockidx+blockidx1, _params);
+                        ablas.rmatrixgemv(kx, kx, -1.0, blockata, celloffset, 0, 1, b, blockidx*kx, 1.0, b, (blockidx+blockidx1)*kx, _params);
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function computes residuals for dataset XY[], using array of original
+        values YRaw[], and loads residuals to XY.
+
+        Processing is performed in parallel manner.
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void computeresidualsfromscratch(double[] xy,
+            double[] yraw,
+            int npoints,
+            int d,
+            int scalexy,
+            spline2dinterpolant spline,
+            alglib.xparams _params)
+        {
+            apserv.srealarray seed = new apserv.srealarray();
+            alglib.smp.shared_pool pool = new alglib.smp.shared_pool();
+            int chunksize = 0;
+            double pointcost = 0;
+
+            
+            //
+            // Setting up
+            //
+            chunksize = 1000;
+            pointcost = 100.0;
+            if( (double)(npoints*pointcost)>(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_computeresidualsfromscratch(xy,yraw,npoints,d,scalexy,spline, _params) )
+                {
+                    return;
+                }
+            }
+            alglib.smp.ae_shared_pool_set_seed(pool, seed);
+            
+            //
+            // Call compute workhorse
+            //
+            computeresidualsfromscratchrec(xy, yraw, 0, npoints, chunksize, d, scalexy, spline, pool, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_computeresidualsfromscratch(double[] xy,
+            double[] yraw,
+            int npoints,
+            int d,
+            int scalexy,
+            spline2dinterpolant spline, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        Recursive workhorse for ComputeResidualsFromScratch.
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void computeresidualsfromscratchrec(double[] xy,
+            double[] yraw,
+            int pt0,
+            int pt1,
+            int chunksize,
+            int d,
+            int scalexy,
+            spline2dinterpolant spline,
+            alglib.smp.shared_pool pool,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            apserv.srealarray pbuf = null;
+            int xew = 0;
+
+            xew = 2+d;
+            
+            //
+            // Parallelism
+            //
+            if( pt1-pt0>chunksize )
+            {
+                apserv.tiledsplit(pt1-pt0, chunksize, ref i, ref j, _params);
+                computeresidualsfromscratchrec(xy, yraw, pt0, pt0+i, chunksize, d, scalexy, spline, pool, _params);
+                computeresidualsfromscratchrec(xy, yraw, pt0+i, pt1, chunksize, d, scalexy, spline, pool, _params);
+                return;
+            }
+            
+            //
+            // Serial execution
+            //
+            alglib.smp.ae_shared_pool_retrieve(pool, ref pbuf);
+            for(i=pt0; i<=pt1-1; i++)
+            {
+                spline2dcalcvbuf(spline, xy[i*xew+0]*scalexy, xy[i*xew+1]*scalexy, ref pbuf.val, _params);
+                for(j=0; j<=d-1; j++)
+                {
+                    xy[i*xew+2+j] = yraw[i*d+j]-pbuf.val[j];
+                }
+            }
+            alglib.smp.ae_shared_pool_recycle(pool, ref pbuf);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_computeresidualsfromscratchrec(double[] xy,
+            double[] yraw,
+            int pt0,
+            int pt1,
+            int chunksize,
+            int d,
+            int scalexy,
+            spline2dinterpolant spline,
+            alglib.smp.shared_pool pool, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        This function reorders dataset and builds index:
+        * it is assumed that all points have X in [0,KX-1], Y in [0,KY-1]
+        * area is divided into (KX-1)*(KY-1) cells
+        * all points are reordered in such way that points in same cell are stored
+          contiguously
+        * dataset index, array[(KX-1)*(KY-1)+1], is generated. Points of cell I
+          now have indexes XYIndex[I]..XYIndex[I+1]-1;
+
+        INPUT PARAMETERS:
+            XY              -   array[NPoints*(2+D)], dataset
+            KX, KY, D       -   grid size and dimensionality of the outputs
+            Shadow          -   shadow array[NPoints*NS], which is sorted together
+                                with XY; if NS=0, it is not referenced at all.
+            NS              -   entry width of shadow array
+            BufI            -   possibly preallocated temporary buffer; resized if
+                                needed.
+
+        OUTPUT PARAMETERS:
+            XY              -   reordered
+            XYIndex         -   array[(KX-1)*(KY-1)+1], dataset index
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void reorderdatasetandbuildindex(double[] xy,
+            int npoints,
+            int d,
+            double[] shadow,
+            int ns,
+            int kx,
+            int ky,
+            ref int[] xyindex,
+            ref int[] bufi,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int entrywidth = 0;
+
+            
+            //
+            // Set up
+            //
+            alglib.ap.assert(kx>=2, "Spline2DFit.ReorderDatasetAndBuildIndex: integrity check failed");
+            alglib.ap.assert(ky>=2, "Spline2DFit.ReorderDatasetAndBuildIndex: integrity check failed");
+            entrywidth = 2+d;
+            apserv.ivectorsetlengthatleast(ref xyindex, (kx-1)*(ky-1)+1, _params);
+            apserv.ivectorsetlengthatleast(ref bufi, npoints, _params);
+            for(i=0; i<=npoints-1; i++)
+            {
+                i0 = apserv.iboundval((int)Math.Floor(xy[i*entrywidth+0]), 0, kx-2, _params);
+                i1 = apserv.iboundval((int)Math.Floor(xy[i*entrywidth+1]), 0, ky-2, _params);
+                bufi[i] = i1*(kx-1)+i0;
+            }
+            
+            //
+            // Reorder
+            //
+            reorderdatasetandbuildindexrec(xy, d, shadow, ns, bufi, 0, npoints, xyindex, 0, (kx-1)*(ky-1), true, _params);
+            xyindex[(kx-1)*(ky-1)] = npoints;
+        }
+
+
+        /*************************************************************************
+        This function multiplies all points in dataset by 2.0 and rebuilds  index,
+        given previous index built for KX_prev=(KX-1)/2 and KY_prev=(KY-1)/2
+
+        INPUT PARAMETERS:
+            XY              -   array[NPoints*(2+D)], dataset BEFORE scaling
+            NPoints, D      -   dataset size and dimensionality of the outputs
+            Shadow          -   shadow array[NPoints*NS], which is sorted together
+                                with XY; if NS=0, it is not referenced at all.
+            NS              -   entry width of shadow array
+            KX, KY          -   new grid dimensionality
+            XYIndex         -   index built for previous values of KX and KY
+            BufI            -   possibly preallocated temporary buffer; resized if
+                                needed.
+
+        OUTPUT PARAMETERS:
+            XY              -   reordered and multiplied by 2.0
+            XYIndex         -   array[(KX-1)*(KY-1)+1], dataset index
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rescaledatasetandrefineindex(double[] xy,
+            int npoints,
+            int d,
+            double[] shadow,
+            int ns,
+            int kx,
+            int ky,
+            ref int[] xyindex,
+            ref int[] bufi,
+            alglib.xparams _params)
+        {
+            int[] xyindexprev = new int[0];
+
+            
+            //
+            // Set up
+            //
+            alglib.ap.assert(kx>=2, "Spline2DFit.RescaleDataset2AndRefineIndex: integrity check failed");
+            alglib.ap.assert(ky>=2, "Spline2DFit.RescaleDataset2AndRefineIndex: integrity check failed");
+            alglib.ap.assert((kx-1)%2==0, "Spline2DFit.RescaleDataset2AndRefineIndex: integrity check failed");
+            alglib.ap.assert((ky-1)%2==0, "Spline2DFit.RescaleDataset2AndRefineIndex: integrity check failed");
+            alglib.ap.swap(ref xyindex, ref xyindexprev);
+            apserv.ivectorsetlengthatleast(ref xyindex, (kx-1)*(ky-1)+1, _params);
+            apserv.ivectorsetlengthatleast(ref bufi, npoints, _params);
+            
+            //
+            // Refine
+            //
+            expandindexrows(xy, d, shadow, ns, bufi, 0, npoints, xyindexprev, 0, (ky+1)/2-1, xyindex, kx, ky, true, _params);
+            xyindex[(kx-1)*(ky-1)] = npoints;
+            
+            //
+            // Integrity check
+            //
+        }
+
+
+        /*************************************************************************
+        Recurrent divide-and-conquer indexing function
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void expandindexrows(double[] xy,
+            int d,
+            double[] shadow,
+            int ns,
+            int[] cidx,
+            int pt0,
+            int pt1,
+            int[] xyindexprev,
+            int row0,
+            int row1,
+            int[] xyindexnew,
+            int kxnew,
+            int kynew,
+            bool rootcall,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int entrywidth = 0;
+            int kxprev = 0;
+            double v = 0;
+            int i0 = 0;
+            int i1 = 0;
+            double efficiency = 0;
+            double cost = 0;
+            int rowmid = 0;
+
+            kxprev = (kxnew+1)/2;
+            entrywidth = 2+d;
+            efficiency = 0.1;
+            cost = d*(pt1-pt0+1)*(Math.Log(kxnew)/Math.Log(2))/efficiency;
+            alglib.ap.assert(xyindexprev[row0*(kxprev-1)+0]==pt0, "Spline2DFit.ExpandIndexRows: integrity check failed");
+            alglib.ap.assert(xyindexprev[row1*(kxprev-1)+0]==pt1, "Spline2DFit.ExpandIndexRows: integrity check failed");
+            
+            //
+            // Parallelism
+            //
+            if( ((rootcall && pt1-pt0>10000) && row1-row0>=2) && (double)(cost)>(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_expandindexrows(xy,d,shadow,ns,cidx,pt0,pt1,xyindexprev,row0,row1,xyindexnew,kxnew,kynew,rootcall, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Partition
+            //
+            if( row1-row0>=2 )
+            {
+                apserv.tiledsplit(row1-row0, 1, ref i0, ref i1, _params);
+                rowmid = row0+i0;
+                expandindexrows(xy, d, shadow, ns, cidx, pt0, xyindexprev[rowmid*(kxprev-1)+0], xyindexprev, row0, rowmid, xyindexnew, kxnew, kynew, false, _params);
+                expandindexrows(xy, d, shadow, ns, cidx, xyindexprev[rowmid*(kxprev-1)+0], pt1, xyindexprev, rowmid, row1, xyindexnew, kxnew, kynew, false, _params);
+                return;
+            }
+            
+            //
+            // Serial execution
+            //
+            for(i=pt0; i<=pt1-1; i++)
+            {
+                v = 2*xy[i*entrywidth+0];
+                xy[i*entrywidth+0] = v;
+                i0 = apserv.iboundval((int)Math.Floor(v), 0, kxnew-2, _params);
+                v = 2*xy[i*entrywidth+1];
+                xy[i*entrywidth+1] = v;
+                i1 = apserv.iboundval((int)Math.Floor(v), 0, kynew-2, _params);
+                cidx[i] = i1*(kxnew-1)+i0;
+            }
+            reorderdatasetandbuildindexrec(xy, d, shadow, ns, cidx, pt0, pt1, xyindexnew, 2*row0*(kxnew-1)+0, 2*row1*(kxnew-1)+0, false, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_expandindexrows(double[] xy,
+            int d,
+            double[] shadow,
+            int ns,
+            int[] cidx,
+            int pt0,
+            int pt1,
+            int[] xyindexprev,
+            int row0,
+            int row1,
+            int[] xyindexnew,
+            int kxnew,
+            int kynew,
+            bool rootcall, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        Recurrent divide-and-conquer indexing function
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void reorderdatasetandbuildindexrec(double[] xy,
+            int d,
+            double[] shadow,
+            int ns,
+            int[] cidx,
+            int pt0,
+            int pt1,
+            int[] xyindex,
+            int idx0,
+            int idx1,
+            bool rootcall,
+            alglib.xparams _params)
+        {
+            int entrywidth = 0;
+            int idxmid = 0;
+            int wrk0 = 0;
+            int wrk1 = 0;
+            double efficiency = 0;
+            double cost = 0;
+
+            
+            //
+            // Efficiency - performance of the code when compared with that
+            // of linear algebra code.
+            //
+            entrywidth = 2+d;
+            efficiency = 0.1;
+            cost = d*(pt1-pt0+1)*Math.Log(idx1-idx0+1)/Math.Log(2)/efficiency;
+            
+            //
+            // Parallelism
+            //
+            if( ((rootcall && pt1-pt0>10000) && idx1-idx0>=2) && (double)(cost)>(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_reorderdatasetandbuildindexrec(xy,d,shadow,ns,cidx,pt0,pt1,xyindex,idx0,idx1,rootcall, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Store left bound to XYIndex
+            //
+            xyindex[idx0] = pt0;
+            
+            //
+            // Quick exit strategies
+            //
+            if( idx1<=idx0+1 )
+            {
+                return;
+            }
+            if( pt0==pt1 )
+            {
+                for(idxmid=idx0+1; idxmid<=idx1-1; idxmid++)
+                {
+                    xyindex[idxmid] = pt1;
+                }
+                return;
+            }
+            
+            //
+            // Select middle element
+            //
+            idxmid = idx0+(idx1-idx0)/2;
+            alglib.ap.assert(idx0<idxmid && idxmid<idx1, "Spline2D: integrity check failed");
+            wrk0 = pt0;
+            wrk1 = pt1-1;
+            while( true )
+            {
+                while( wrk0<pt1 && cidx[wrk0]<idxmid )
+                {
+                    wrk0 = wrk0+1;
+                }
+                while( wrk1>=pt0 && cidx[wrk1]>=idxmid )
+                {
+                    wrk1 = wrk1-1;
+                }
+                if( wrk1<=wrk0 )
+                {
+                    break;
+                }
+                apserv.swapentries(xy, wrk0, wrk1, entrywidth, _params);
+                if( ns>0 )
+                {
+                    apserv.swapentries(shadow, wrk0, wrk1, ns, _params);
+                }
+                apserv.swapelementsi(cidx, wrk0, wrk1, _params);
+            }
+            reorderdatasetandbuildindexrec(xy, d, shadow, ns, cidx, pt0, wrk0, xyindex, idx0, idxmid, false, _params);
+            reorderdatasetandbuildindexrec(xy, d, shadow, ns, cidx, wrk0, pt1, xyindex, idxmid, idx1, false, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_reorderdatasetandbuildindexrec(double[] xy,
+            int d,
+            double[] shadow,
+            int ns,
+            int[] cidx,
+            int pt0,
+            int pt1,
+            int[] xyindex,
+            int idx0,
+            int idx1,
+            bool rootcall, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        This function performs fitting with  BlockLLS solver.  Internal  function,
+        never use it directly.
+
+        INPUT PARAMETERS:
+            XY      -   dataset, array[NPoints,2+D]
+            XYIndex -   dataset index, see ReorderDatasetAndBuildIndex() for more info
+            KX0, KX1-   X-indices of basis functions to select and fit;
+                        range [KX0,KX1) is processed
+            KXTotal -   total number of indexes in the entire grid
+            KY0, KY1-   Y-indices of basis functions to select and fit;
+                        range [KY0,KY1) is processed
+            KYTotal -   total number of indexes in the entire grid
+            D       -   number of components in vector-valued spline, D>=1
+            LambdaReg-  regularization coefficient
+            LambdaNS-   nonlinearity penalty, exactly zero value is specially handled
+                        (entire set of rows is not added to the matrix)
+            Basis1  -   single-dimensional B-spline
+            
+
+        OUTPUT PARAMETERS:
+            A       -   design matrix
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void xdesigngenerate(double[] xy,
+            int[] xyindex,
+            int kx0,
+            int kx1,
+            int kxtotal,
+            int ky0,
+            int ky1,
+            int kytotal,
+            int d,
+            double lambdareg,
+            double lambdans,
+            spline1d.spline1dinterpolant basis1,
+            spline2dxdesignmatrix a,
+            alglib.xparams _params)
+        {
+            int entrywidth = 0;
+            int i = 0;
+            int j = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int k0 = 0;
+            int k1 = 0;
+            int kx = 0;
+            int ky = 0;
+            int rowsdone = 0;
+            int batchesdone = 0;
+            int pt0 = 0;
+            int pt1 = 0;
+            int base0 = 0;
+            int base1 = 0;
+            int baseidx = 0;
+            int nzshift = 0;
+            int nzwidth = 0;
+            double[,] d2x = new double[0,0];
+            double[,] d2y = new double[0,0];
+            double[,] dxy = new double[0,0];
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double w0 = 0;
+            double w1 = 0;
+            double w2 = 0;
+
+            nzshift = 1;
+            nzwidth = 4;
+            entrywidth = 2+d;
+            kx = kx1-kx0;
+            ky = ky1-ky0;
+            a.lambdareg = lambdareg;
+            a.blockwidth = 4;
+            a.kx = kx;
+            a.ky = ky;
+            a.d = d;
+            a.npoints = 0;
+            a.ndenserows = 0;
+            a.ndensebatches = 0;
+            a.maxbatch = 0;
+            for(j1=ky0; j1<=ky1-2; j1++)
+            {
+                for(j0=kx0; j0<=kx1-2; j0++)
+                {
+                    i = xyindex[j1*(kxtotal-1)+j0+1]-xyindex[j1*(kxtotal-1)+j0];
+                    a.npoints = a.npoints+i;
+                    a.ndenserows = a.ndenserows+i;
+                    a.ndensebatches = a.ndensebatches+1;
+                    a.maxbatch = Math.Max(a.maxbatch, i);
+                }
+            }
+            if( (double)(lambdans)!=(double)(0) )
+            {
+                alglib.ap.assert((double)(lambdans)>=(double)(0), "Spline2DFit: integrity check failed");
+                a.ndenserows = a.ndenserows+3*(kx-2)*(ky-2);
+                a.ndensebatches = a.ndensebatches+(kx-2)*(ky-2);
+                a.maxbatch = Math.Max(a.maxbatch, 3);
+            }
+            a.nrows = a.ndenserows+kx*ky;
+            apserv.rmatrixsetlengthatleast(ref a.vals, a.ndenserows, a.blockwidth*a.blockwidth+d, _params);
+            apserv.ivectorsetlengthatleast(ref a.batches, a.ndensebatches+1, _params);
+            apserv.ivectorsetlengthatleast(ref a.batchbases, a.ndensebatches, _params);
+            
+            //
+            // Setup output counters
+            //
+            batchesdone = 0;
+            rowsdone = 0;
+            
+            //
+            // Generate rows corresponding to dataset points
+            //
+            alglib.ap.assert(kx>=nzwidth, "Spline2DFit: integrity check failed");
+            alglib.ap.assert(ky>=nzwidth, "Spline2DFit: integrity check failed");
+            apserv.rvectorsetlengthatleast(ref a.tmp0, nzwidth, _params);
+            apserv.rvectorsetlengthatleast(ref a.tmp1, nzwidth, _params);
+            a.batches[batchesdone] = 0;
+            for(j1=ky0; j1<=ky1-2; j1++)
+            {
+                for(j0=kx0; j0<=kx1-2; j0++)
+                {
+                    pt0 = xyindex[j1*(kxtotal-1)+j0];
+                    pt1 = xyindex[j1*(kxtotal-1)+j0+1];
+                    base0 = apserv.iboundval(j0-kx0-nzshift, 0, kx-nzwidth, _params);
+                    base1 = apserv.iboundval(j1-ky0-nzshift, 0, ky-nzwidth, _params);
+                    baseidx = base1*kx+base0;
+                    a.batchbases[batchesdone] = baseidx;
+                    for(i=pt0; i<=pt1-1; i++)
+                    {
+                        for(k0=0; k0<=nzwidth-1; k0++)
+                        {
+                            a.tmp0[k0] = spline1d.spline1dcalc(basis1, xy[i*entrywidth+0]-(base0+kx0+k0), _params);
+                        }
+                        for(k1=0; k1<=nzwidth-1; k1++)
+                        {
+                            a.tmp1[k1] = spline1d.spline1dcalc(basis1, xy[i*entrywidth+1]-(base1+ky0+k1), _params);
+                        }
+                        for(k1=0; k1<=nzwidth-1; k1++)
+                        {
+                            for(k0=0; k0<=nzwidth-1; k0++)
+                            {
+                                a.vals[rowsdone,k1*nzwidth+k0] = a.tmp0[k0]*a.tmp1[k1];
+                            }
+                        }
+                        for(j=0; j<=d-1; j++)
+                        {
+                            a.vals[rowsdone,nzwidth*nzwidth+j] = xy[i*entrywidth+2+j];
+                        }
+                        rowsdone = rowsdone+1;
+                    }
+                    batchesdone = batchesdone+1;
+                    a.batches[batchesdone] = rowsdone;
+                }
+            }
+            
+            //
+            // Generate rows corresponding to nonlinearity penalty
+            //
+            if( (double)(lambdans)>(double)(0) )
+            {
+                
+                //
+                // Smoothing is applied. Because all grid nodes are same,
+                // we apply same smoothing kernel, which is calculated only
+                // once at the beginning of design matrix generation.
+                //
+                d2x = new double[3, 3];
+                d2y = new double[3, 3];
+                dxy = new double[3, 3];
+                for(j1=0; j1<=2; j1++)
+                {
+                    for(j0=0; j0<=2; j0++)
+                    {
+                        d2x[j0,j1] = 0.0;
+                        d2y[j0,j1] = 0.0;
+                        dxy[j0,j1] = 0.0;
+                    }
+                }
+                for(k1=0; k1<=2; k1++)
+                {
+                    for(k0=0; k0<=2; k0++)
+                    {
+                        spline1d.spline1ddiff(basis1, -(k0-1), ref v0, ref v1, ref v2, _params);
+                        spline1d.spline1ddiff(basis1, -(k1-1), ref w0, ref w1, ref w2, _params);
+                        d2x[k0,k1] = d2x[k0,k1]+v2*w0;
+                        d2y[k0,k1] = d2y[k0,k1]+w2*v0;
+                        dxy[k0,k1] = dxy[k0,k1]+v1*w1;
+                    }
+                }
+                
+                //
+                // Now, kernel is ready - apply it to all inner nodes of the grid.
+                //
+                for(j1=1; j1<=ky-2; j1++)
+                {
+                    for(j0=1; j0<=kx-2; j0++)
+                    {
+                        base0 = apserv.imax2(j0-2, 0, _params);
+                        base1 = apserv.imax2(j1-2, 0, _params);
+                        baseidx = base1*kx+base0;
+                        a.batchbases[batchesdone] = baseidx;
+                        
+                        //
+                        // d2F/dx2 term
+                        //
+                        v = lambdans;
+                        for(j=0; j<=nzwidth*nzwidth+d-1; j++)
+                        {
+                            a.vals[rowsdone,j] = 0;
+                        }
+                        for(k1=j1-1; k1<=j1+1; k1++)
+                        {
+                            for(k0=j0-1; k0<=j0+1; k0++)
+                            {
+                                a.vals[rowsdone,nzwidth*(k1-base1)+(k0-base0)] = v*d2x[1+(k0-j0),1+(k1-j1)];
+                            }
+                        }
+                        rowsdone = rowsdone+1;
+                        
+                        //
+                        // d2F/dy2 term
+                        //
+                        v = lambdans;
+                        for(j=0; j<=nzwidth*nzwidth+d-1; j++)
+                        {
+                            a.vals[rowsdone,j] = 0;
+                        }
+                        for(k1=j1-1; k1<=j1+1; k1++)
+                        {
+                            for(k0=j0-1; k0<=j0+1; k0++)
+                            {
+                                a.vals[rowsdone,nzwidth*(k1-base1)+(k0-base0)] = v*d2y[1+(k0-j0),1+(k1-j1)];
+                            }
+                        }
+                        rowsdone = rowsdone+1;
+                        
+                        //
+                        // 2*d2F/dxdy term
+                        //
+                        v = Math.Sqrt(2)*lambdans;
+                        for(j=0; j<=nzwidth*nzwidth+d-1; j++)
+                        {
+                            a.vals[rowsdone,j] = 0;
+                        }
+                        for(k1=j1-1; k1<=j1+1; k1++)
+                        {
+                            for(k0=j0-1; k0<=j0+1; k0++)
+                            {
+                                a.vals[rowsdone,nzwidth*(k1-base1)+(k0-base0)] = v*dxy[1+(k0-j0),1+(k1-j1)];
+                            }
+                        }
+                        rowsdone = rowsdone+1;
+                        batchesdone = batchesdone+1;
+                        a.batches[batchesdone] = rowsdone;
+                    }
+                }
+            }
+            
+            //
+            // Integrity post-check
+            //
+            alglib.ap.assert(batchesdone==a.ndensebatches, "Spline2DFit: integrity check failed");
+            alglib.ap.assert(rowsdone==a.ndenserows, "Spline2DFit: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This function performs matrix-vector product of design matrix and dense
+        vector.
+
+        INPUT PARAMETERS:
+            A       -   design matrix, (a.nrows) X (a.kx*a.ky);
+                        some fields of A are used for temporaries,
+                        so it is non-constant.
+            X       -   array[A.KX*A.KY]
+            
+
+        OUTPUT PARAMETERS:
+            Y       -   product, array[A.NRows], automatically allocated
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void xdesignmv(spline2dxdesignmatrix a,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int bidx = 0;
+            int i = 0;
+            int cnt = 0;
+            double v = 0;
+            int baseidx = 0;
+            int outidx = 0;
+            int batchsize = 0;
+            int kx = 0;
+            int k0 = 0;
+            int k1 = 0;
+            int nzwidth = 0;
+
+            nzwidth = 4;
+            alglib.ap.assert(a.blockwidth==nzwidth, "Spline2DFit: integrity check failed");
+            alglib.ap.assert(alglib.ap.len(x)>=a.kx*a.ky, "Spline2DFit: integrity check failed");
+            
+            //
+            // Prepare
+            //
+            apserv.rvectorsetlengthatleast(ref y, a.nrows, _params);
+            apserv.rvectorsetlengthatleast(ref a.tmp0, nzwidth*nzwidth, _params);
+            apserv.rvectorsetlengthatleast(ref a.tmp1, a.maxbatch, _params);
+            kx = a.kx;
+            outidx = 0;
+            
+            //
+            // Process dense part
+            //
+            for(bidx=0; bidx<=a.ndensebatches-1; bidx++)
+            {
+                if( a.batches[bidx+1]-a.batches[bidx]>0 )
+                {
+                    batchsize = a.batches[bidx+1]-a.batches[bidx];
+                    baseidx = a.batchbases[bidx];
+                    for(k1=0; k1<=nzwidth-1; k1++)
+                    {
+                        for(k0=0; k0<=nzwidth-1; k0++)
+                        {
+                            a.tmp0[k1*nzwidth+k0] = x[baseidx+k1*kx+k0];
+                        }
+                    }
+                    ablas.rmatrixgemv(batchsize, nzwidth*nzwidth, 1.0, a.vals, a.batches[bidx], 0, 0, a.tmp0, 0, 0.0, a.tmp1, 0, _params);
+                    for(i=0; i<=batchsize-1; i++)
+                    {
+                        y[outidx+i] = a.tmp1[i];
+                    }
+                    outidx = outidx+batchsize;
+                }
+            }
+            alglib.ap.assert(outidx==a.ndenserows, "Spline2DFit: integrity check failed");
+            
+            //
+            // Process regularizer 
+            //
+            v = a.lambdareg;
+            cnt = a.kx*a.ky;
+            for(i=0; i<=cnt-1; i++)
+            {
+                y[outidx+i] = v*x[i];
+            }
+            outidx = outidx+cnt;
+            
+            //
+            // Post-check
+            //
+            alglib.ap.assert(outidx==a.nrows, "Spline2DFit: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This function performs matrix-vector product of transposed design matrix and dense
+        vector.
+
+        INPUT PARAMETERS:
+            A       -   design matrix, (a.nrows) X (a.kx*a.ky);
+                        some fields of A are used for temporaries,
+                        so it is non-constant.
+            X       -   array[A.NRows]
+            
+
+        OUTPUT PARAMETERS:
+            Y       -   product, array[A.KX*A.KY], automatically allocated
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void xdesignmtv(spline2dxdesignmatrix a,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int bidx = 0;
+            int i = 0;
+            int cnt = 0;
+            double v = 0;
+            int baseidx = 0;
+            int inidx = 0;
+            int batchsize = 0;
+            int kx = 0;
+            int k0 = 0;
+            int k1 = 0;
+            int nzwidth = 0;
+
+            nzwidth = 4;
+            alglib.ap.assert(a.blockwidth==nzwidth, "Spline2DFit: integrity check failed");
+            alglib.ap.assert(alglib.ap.len(x)>=a.nrows, "Spline2DFit: integrity check failed");
+            
+            //
+            // Prepare
+            //
+            apserv.rvectorsetlengthatleast(ref y, a.kx*a.ky, _params);
+            apserv.rvectorsetlengthatleast(ref a.tmp0, nzwidth*nzwidth, _params);
+            apserv.rvectorsetlengthatleast(ref a.tmp1, a.maxbatch, _params);
+            kx = a.kx;
+            inidx = 0;
+            cnt = a.kx*a.ky;
+            for(i=0; i<=cnt-1; i++)
+            {
+                y[i] = 0;
+            }
+            
+            //
+            // Process dense part
+            //
+            for(bidx=0; bidx<=a.ndensebatches-1; bidx++)
+            {
+                if( a.batches[bidx+1]-a.batches[bidx]>0 )
+                {
+                    batchsize = a.batches[bidx+1]-a.batches[bidx];
+                    baseidx = a.batchbases[bidx];
+                    for(i=0; i<=batchsize-1; i++)
+                    {
+                        a.tmp1[i] = x[inidx+i];
+                    }
+                    ablas.rmatrixgemv(nzwidth*nzwidth, batchsize, 1.0, a.vals, a.batches[bidx], 0, 1, a.tmp1, 0, 0.0, a.tmp0, 0, _params);
+                    for(k1=0; k1<=nzwidth-1; k1++)
+                    {
+                        for(k0=0; k0<=nzwidth-1; k0++)
+                        {
+                            y[baseidx+k1*kx+k0] = y[baseidx+k1*kx+k0]+a.tmp0[k1*nzwidth+k0];
+                        }
+                    }
+                    inidx = inidx+batchsize;
+                }
+            }
+            alglib.ap.assert(inidx==a.ndenserows, "Spline2DFit: integrity check failed");
+            
+            //
+            // Process regularizer 
+            //
+            v = a.lambdareg;
+            cnt = a.kx*a.ky;
+            for(i=0; i<=cnt-1; i++)
+            {
+                y[i] = y[i]+v*x[inidx+i];
+            }
+            inidx = inidx+cnt;
+            
+            //
+            // Post-check
+            //
+            alglib.ap.assert(inidx==a.nrows, "Spline2DFit: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This function generates squared design matrix stored in block band format.
+
+        We  use  an   adaptation   of   block   skyline   storage   format,   with
+        TOWERSIZE*KX   skyline    bands   (towers)   stored   sequentially;   here
+        TOWERSIZE=(BlockBandwidth+1)*KX. So, we have KY "towers", stored one below
+        other, in BlockATA matrix. Every "tower" is a sequence of BlockBandwidth+1
+        cells, each of them being KX*KX in size.
+
+        INPUT PARAMETERS:
+            A       -   design matrix; some of its fields are used for temporaries
+            BlockATA-   array[KY*(BlockBandwidth+1)*KX,KX],  preallocated  storage
+                        for output matrix in compressed block band format
+
+        OUTPUT PARAMETERS:
+            BlockATA-   AH*AH', stored in compressed block band format
+            MXATA   -   max(|AH*AH'|), elementwise
+
+          -- ALGLIB --
+             Copyright 05.02.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void xdesignblockata(spline2dxdesignmatrix a,
+            double[,] blockata,
+            ref double mxata,
+            alglib.xparams _params)
+        {
+            int blockbandwidth = 0;
+            int nzwidth = 0;
+            int kx = 0;
+            int ky = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int celloffset = 0;
+            int bidx = 0;
+            int baseidx = 0;
+            int batchsize = 0;
+            int offs0 = 0;
+            int offs1 = 0;
+            double v = 0;
+
+            blockbandwidth = 3;
+            nzwidth = 4;
+            kx = a.kx;
+            ky = a.ky;
+            alglib.ap.assert(a.blockwidth==nzwidth, "Spline2DFit: integrity check failed");
+            apserv.rmatrixsetlengthatleast(ref a.tmp2, nzwidth*nzwidth, nzwidth*nzwidth, _params);
+            
+            //
+            // Initial zero-fill:
+            // * zero-fill ALL elements of BlockATA
+            // * zero-fill ALL elements of Tmp2
+            //
+            // Filling ALL elements, including unused ones, is essential for the
+            // purposes of calculating max(BlockATA).
+            //
+            for(i1=0; i1<=ky-1; i1++)
+            {
+                for(i0=i1; i0<=Math.Min(ky-1, i1+blockbandwidth); i0++)
+                {
+                    celloffset = getcelloffset(kx, ky, blockbandwidth, i1, i0, _params);
+                    for(j1=0; j1<=kx-1; j1++)
+                    {
+                        for(j0=0; j0<=kx-1; j0++)
+                        {
+                            blockata[celloffset+j1,j0] = 0.0;
+                        }
+                    }
+                }
+            }
+            for(j1=0; j1<=nzwidth*nzwidth-1; j1++)
+            {
+                for(j0=0; j0<=nzwidth*nzwidth-1; j0++)
+                {
+                    a.tmp2[j1,j0] = 0.0;
+                }
+            }
+            
+            //
+            // Process dense part of A
+            //
+            for(bidx=0; bidx<=a.ndensebatches-1; bidx++)
+            {
+                if( a.batches[bidx+1]-a.batches[bidx]>0 )
+                {
+                    
+                    //
+                    // Generate 16x16 U = BATCH'*BATCH and add it to ATA.
+                    //
+                    // NOTE: it is essential that lower triangle of Tmp2 is
+                    //       filled by zeros.
+                    //
+                    batchsize = a.batches[bidx+1]-a.batches[bidx];
+                    ablas.rmatrixsyrk(nzwidth*nzwidth, batchsize, 1.0, a.vals, a.batches[bidx], 0, 2, 0.0, a.tmp2, 0, 0, true, _params);
+                    baseidx = a.batchbases[bidx];
+                    for(i1=0; i1<=nzwidth-1; i1++)
+                    {
+                        for(j1=i1; j1<=nzwidth-1; j1++)
+                        {
+                            celloffset = getcelloffset(kx, ky, blockbandwidth, baseidx/kx+i1, baseidx/kx+j1, _params);
+                            offs0 = baseidx%kx;
+                            offs1 = baseidx%kx;
+                            for(i0=0; i0<=nzwidth-1; i0++)
+                            {
+                                for(j0=0; j0<=nzwidth-1; j0++)
+                                {
+                                    v = a.tmp2[i1*nzwidth+i0,j1*nzwidth+j0];
+                                    blockata[celloffset+offs1+i0,offs0+j0] = blockata[celloffset+offs1+i0,offs0+j0]+v;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Process regularizer term
+            //
+            for(i1=0; i1<=ky-1; i1++)
+            {
+                celloffset = getcelloffset(kx, ky, blockbandwidth, i1, i1, _params);
+                for(j1=0; j1<=kx-1; j1++)
+                {
+                    blockata[celloffset+j1,j1] = blockata[celloffset+j1,j1]+math.sqr(a.lambdareg);
+                }
+            }
+            
+            //
+            // Calculate max(ATA)
+            //
+            // NOTE: here we rely on zero initialization of unused parts of
+            //       BlockATA and Tmp2.
+            //
+            mxata = 0.0;
+            for(i1=0; i1<=ky-1; i1++)
+            {
+                for(i0=i1; i0<=Math.Min(ky-1, i1+blockbandwidth); i0++)
+                {
+                    celloffset = getcelloffset(kx, ky, blockbandwidth, i1, i0, _params);
+                    for(j1=0; j1<=kx-1; j1++)
+                    {
+                        for(j0=0; j0<=kx-1; j0++)
+                        {
+                            mxata = Math.Max(mxata, Math.Abs(blockata[celloffset+j1,j0]));
+                        }
+                    }
+                }
+            }
+        }
+
+
+    }
+    public class rbfv1
+    {
+        /*************************************************************************
+        Buffer object which is used to perform nearest neighbor  requests  in  the
+        multithreaded mode (multiple threads working with same KD-tree object).
+
+        This object should be created with KDTreeCreateBuffer().
+        *************************************************************************/
+        public class rbfv1calcbuffer : apobject
+        {
+            public double[] calcbufxcx;
+            public double[,] calcbufx;
+            public int[] calcbuftags;
+            public nearestneighbor.kdtreerequestbuffer requestbuffer;
+            public rbfv1calcbuffer()
+            {
+                init();
+            }
+            public override void init()
+            {
+                calcbufxcx = new double[0];
+                calcbufx = new double[0,0];
+                calcbuftags = new int[0];
+                requestbuffer = new nearestneighbor.kdtreerequestbuffer();
+            }
+            public override alglib.apobject make_copy()
+            {
+                rbfv1calcbuffer _result = new rbfv1calcbuffer();
+                _result.calcbufxcx = (double[])calcbufxcx.Clone();
+                _result.calcbufx = (double[,])calcbufx.Clone();
+                _result.calcbuftags = (int[])calcbuftags.Clone();
+                _result.requestbuffer = (nearestneighbor.kdtreerequestbuffer)requestbuffer.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        RBF model.
+
+        Never try to directly work with fields of this object - always use  ALGLIB
+        functions to use this object.
+        *************************************************************************/
+        public class rbfv1model : apobject
+        {
+            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 double[] calcbufxcx;
+            public double[,] calcbufx;
+            public int[] calcbuftags;
+            public rbfv1model()
+            {
+                init();
+            }
+            public override void init()
+            {
+                tree = new nearestneighbor.kdtree();
+                xc = new double[0,0];
+                wr = new double[0,0];
+                v = new double[0,0];
+                calcbufxcx = new double[0];
+                calcbufx = new double[0,0];
+                calcbuftags = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                rbfv1model _result = new rbfv1model();
+                _result.ny = ny;
+                _result.nx = nx;
+                _result.nc = nc;
+                _result.nl = nl;
+                _result.tree = (nearestneighbor.kdtree)tree.make_copy();
+                _result.xc = (double[,])xc.Clone();
+                _result.wr = (double[,])wr.Clone();
+                _result.rmax = rmax;
+                _result.v = (double[,])v.Clone();
+                _result.calcbufxcx = (double[])calcbufxcx.Clone();
+                _result.calcbufx = (double[,])calcbufx.Clone();
+                _result.calcbuftags = (int[])calcbuftags.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Internal buffer for GridCalc3
+        *************************************************************************/
+        public class gridcalc3v1buf : apobject
+        {
+            public double[] tx;
+            public double[] cx;
+            public double[] ty;
+            public bool[] flag0;
+            public bool[] flag1;
+            public bool[] flag2;
+            public bool[] flag12;
+            public double[] expbuf0;
+            public double[] expbuf1;
+            public double[] expbuf2;
+            public nearestneighbor.kdtreerequestbuffer requestbuf;
+            public double[,] calcbufx;
+            public int[] calcbuftags;
+            public gridcalc3v1buf()
+            {
+                init();
+            }
+            public override void init()
+            {
+                tx = new double[0];
+                cx = new double[0];
+                ty = new double[0];
+                flag0 = new bool[0];
+                flag1 = new bool[0];
+                flag2 = new bool[0];
+                flag12 = new bool[0];
+                expbuf0 = new double[0];
+                expbuf1 = new double[0];
+                expbuf2 = new double[0];
+                requestbuf = new nearestneighbor.kdtreerequestbuffer();
+                calcbufx = new double[0,0];
+                calcbuftags = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                gridcalc3v1buf _result = new gridcalc3v1buf();
+                _result.tx = (double[])tx.Clone();
+                _result.cx = (double[])cx.Clone();
+                _result.ty = (double[])ty.Clone();
+                _result.flag0 = (bool[])flag0.Clone();
+                _result.flag1 = (bool[])flag1.Clone();
+                _result.flag2 = (bool[])flag2.Clone();
+                _result.flag12 = (bool[])flag12.Clone();
+                _result.expbuf0 = (double[])expbuf0.Clone();
+                _result.expbuf1 = (double[])expbuf1.Clone();
+                _result.expbuf2 = (double[])expbuf2.Clone();
+                _result.requestbuf = (nearestneighbor.kdtreerequestbuffer)requestbuf.make_copy();
+                _result.calcbufx = (double[,])calcbufx.Clone();
+                _result.calcbuftags = (int[])calcbuftags.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        RBF solution report:
+        * TerminationType   -   termination type, positive values - success,
+                                non-positive - failure.
+        *************************************************************************/
+        public class rbfv1report : apobject
+        {
+            public int arows;
+            public int acols;
+            public int annz;
+            public int iterationscount;
+            public int nmv;
+            public int terminationtype;
+            public rbfv1report()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                rbfv1report _result = new rbfv1report();
+                _result.arows = arows;
+                _result.acols = acols;
+                _result.annz = annz;
+                _result.iterationscount = iterationscount;
+                _result.nmv = nmv;
+                _result.terminationtype = terminationtype;
+                return _result;
+            }
+        };
+
+
+
+
+        public const int mxnx = 3;
+        public const double rbffarradius = 6;
+        public const double rbfnearradius = 2.1;
+        public const double rbfmlradius = 3;
+        public const double minbasecasecost = 100000;
+
+
+        /*************************************************************************
+        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).
+
+        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)
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfv1create(int nx,
+            int ny,
+            rbfv1model s,
+            alglib.xparams _params)
+        {
+            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.rmax = 0;
+        }
+
+
+        /*************************************************************************
+        This function creates buffer  structure  which  can  be  used  to  perform
+        parallel  RBF  model  evaluations  (with  one  RBF  model  instance  being
+        used from multiple threads, as long as  different  threads  use  different
+        instances of buffer).
+
+        This buffer object can be used with  rbftscalcbuf()  function  (here  "ts"
+        stands for "thread-safe", "buf" is a suffix which denotes  function  which
+        reuses previously allocated output space).
+
+        How to use it:
+        * create RBF model structure with rbfcreate()
+        * load data, tune parameters
+        * call rbfbuildmodel()
+        * call rbfcreatecalcbuffer(), once per thread working with RBF model  (you
+          should call this function only AFTER call to rbfbuildmodel(), see  below
+          for more information)
+        * call rbftscalcbuf() from different threads,  with  each  thread  working
+          with its own copy of buffer object.
+
+        INPUT PARAMETERS
+            S           -   RBF model
+
+        OUTPUT PARAMETERS
+            Buf         -   external buffer.
+            
+            
+        IMPORTANT: buffer object should be used only with  RBF model object  which
+                   was used to initialize buffer. Any attempt to use buffer   with
+                   different object is dangerous - you may  get  memory  violation
+                   error because sizes of internal arrays do not fit to dimensions
+                   of RBF structure.
+                   
+        IMPORTANT: you  should  call  this function only for model which was built
+                   with rbfbuildmodel() function, after successful  invocation  of
+                   rbfbuildmodel().  Sizes   of   some   internal  structures  are
+                   determined only after model is built, so buffer object  created
+                   before model  construction  stage  will  be  useless  (and  any
+                   attempt to use it will result in exception).
+
+          -- ALGLIB --
+             Copyright 02.04.2016 by Sergey Bochkanov
+        *************************************************************************/
+        public static void rbfv1createcalcbuffer(rbfv1model s,
+            rbfv1calcbuffer buf,
+            alglib.xparams _params)
+        {
+            nearestneighbor.kdtreecreaterequestbuffer(s.tree, buf.requestbuffer, _params);
+        }
+
+
+        /*************************************************************************
+        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 rbfv1buildmodel(double[,] x,
+            double[,] y,
+            int n,
+            int aterm,
+            int algorithmtype,
+            int nlayers,
+            double radvalue,
+            double radzvalue,
+            double lambdav,
+            double epsort,
+            double epserr,
+            int maxits,
+            rbfv1model s,
+            rbfv1report rep,
+            alglib.xparams _params)
+        {
+            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[,] residualy = new double[0,0];
+            double[] radius = new double[0];
+            double[,] xc = new double[0,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 snnz = 0;
+            double[] tmp0 = new double[0];
+            double[] tmp1 = new double[0];
+            int layerscnt = 0;
+            bool modelstatus = new bool();
+
+            alglib.ap.assert(s.nx==2 || s.nx==3, "RBFBuildModel: S.NX<>2 or S.NX<>3!");
+            
+            //
+            // Quick exit when we have no points
+            //
+            if( n==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, _params);
+                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[n, s.ny];
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=s.ny-1; j++)
+                {
+                    residualy[i,j] = y[i,j];
+                }
+            }
+            if( !rbfv1buildlinearmodel(x, ref residualy, n, s.ny, aterm, ref v, _params) )
+            {
+                rep.terminationtype = -5;
+                return;
+            }
+            
+            //
+            // Handle special case: multilayer model with NLayers=0.
+            // Quick exit.
+            //
+            if( algorithmtype==2 && 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, _params);
+                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] = v[i,j];
+                    }
+                }
+                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;
+            modelstatus = false;
+            if( algorithmtype==1 )
+            {
+                
+                //
+                // Add RBF model.
+                // This model uses local KD-trees to speed-up nearest neighbor searches.
+                //
+                nc = n;
+                xc = new double[nc, mxnx];
+                for(i=0; i<=nc-1; i++)
+                {
+                    for(j=0; j<=mxnx-1; j++)
+                    {
+                        xc[i,j] = x[i,j];
+                    }
+                }
+                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, _params);
+                if( nc==0 )
+                {
+                    rmax = 1;
+                }
+                else
+                {
+                    if( nc==1 )
+                    {
+                        radius[0] = 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, _params)>0 )
+                            {
+                                nearestneighbor.kdtreequeryresultsdistances(ctree, ref dist, _params);
+                                radius[i] = 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, _params);
+                        for(i=0; i<=nc-1; i++)
+                        {
+                            tmp0[i] = radius[i];
+                        }
+                        tsort.tagsortfast(ref tmp0, ref tmp1, nc, _params);
+                        for(i=0; i<=nc-1; i++)
+                        {
+                            radius[i] = Math.Min(radius[i], 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, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    tags[i] = i;
+                }
+                nearestneighbor.kdtreebuildtagged(x, tags, n, mxnx, 0, 2, tree, _params);
+                buildrbfmodellsqr(x, ref residualy, xc, radius, n, nc, s.ny, tree, ctree, epsort, epserr, maxits, ref rep.annz, ref snnz, ref omega, ref rep.terminationtype, ref rep.iterationscount, ref rep.nmv, _params);
+                layerscnt = 1;
+                modelstatus = true;
+            }
+            if( algorithmtype==2 )
+            {
+                rmax = radvalue;
+                buildrbfmlayersmodellsqr(x, ref residualy, ref xc, radvalue, ref radius, n, ref nc, s.ny, nlayers, ctree, 1.0E-6, 1.0E-6, 50, lambdav, ref rep.annz, ref omega, ref rep.terminationtype, ref rep.iterationscount, ref rep.nmv, _params);
+                layerscnt = nlayers;
+                modelstatus = true;
+            }
+            alglib.ap.assert(modelstatus, "RBFBuildModel: integrity error");
+            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, _params);
+            for(i=0; i<=s.nc-1; i++)
+            {
+                tags[i] = i;
+            }
+            nearestneighbor.kdtreebuildtagged(s.xc, tags, s.nc, mxnx, 0, 2, s.tree, _params);
+            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 = n;
+            rep.acols = s.nc;
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 02.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfv1alloc(alglib.serializer s,
+            rbfv1model model,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Data
+            //
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            s.alloc_entry();
+            nearestneighbor.kdtreealloc(s, model.tree, _params);
+            apserv.allocrealmatrix(s, model.xc, -1, -1, _params);
+            apserv.allocrealmatrix(s, model.wr, -1, -1, _params);
+            s.alloc_entry();
+            apserv.allocrealmatrix(s, model.v, -1, -1, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 02.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfv1serialize(alglib.serializer s,
+            rbfv1model model,
+            alglib.xparams _params)
+        {
+            
+            //
+            // 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, _params);
+            apserv.serializerealmatrix(s, model.xc, -1, -1, _params);
+            apserv.serializerealmatrix(s, model.wr, -1, -1, _params);
+            s.serialize_double(model.rmax);
+            apserv.serializerealmatrix(s, model.v, -1, -1, _params);
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 02.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfv1unserialize(alglib.serializer s,
+            rbfv1model model,
+            alglib.xparams _params)
+        {
+            int nx = 0;
+            int ny = 0;
+
+            
+            //
+            // 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();
+            rbfv1create(nx, ny, model, _params);
+            model.nc = s.unserialize_int();
+            model.nl = s.unserialize_int();
+            nearestneighbor.kdtreeunserialize(s, model.tree, _params);
+            apserv.unserializerealmatrix(s, ref model.xc, _params);
+            apserv.unserializerealmatrix(s, ref model.wr, _params);
+            model.rmax = s.unserialize_double();
+            apserv.unserializerealmatrix(s, ref model.v, _params);
+        }
+
+
+        /*************************************************************************
+        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 rbfv1calc2(rbfv1model s,
+            double x0,
+            double x1,
+            alglib.xparams _params)
+        {
+            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)!");
+            if( s.ny!=1 || s.nx!=2 )
+            {
+                result = 0;
+                return result;
+            }
+            result = s.v[0,0]*x0+s.v[0,1]*x1+s.v[0,mxnx];
+            if( s.nc==0 )
+            {
+                return result;
+            }
+            apserv.rvectorsetlengthatleast(ref s.calcbufxcx, mxnx, _params);
+            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, _params);
+            nearestneighbor.kdtreequeryresultsx(s.tree, ref s.calcbufx, _params);
+            nearestneighbor.kdtreequeryresultstags(s.tree, ref s.calcbuftags, _params);
+            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;
+                }
+            }
+            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 rbfv1calc3(rbfv1model s,
+            double x0,
+            double x1,
+            double x2,
+            alglib.xparams _params)
+        {
+            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)!");
+            if( s.ny!=1 || s.nx!=3 )
+            {
+                result = 0;
+                return result;
+            }
+            result = s.v[0,0]*x0+s.v[0,1]*x1+s.v[0,2]*x2+s.v[0,mxnx];
+            if( s.nc==0 )
+            {
+                return result;
+            }
+            
+            //
+            // calculating value for F(X)
+            //
+            apserv.rvectorsetlengthatleast(ref s.calcbufxcx, mxnx, _params);
+            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, _params);
+            nearestneighbor.kdtreequeryresultsx(s.tree, ref s.calcbufx, _params);
+            nearestneighbor.kdtreequeryresultstags(s.tree, ref s.calcbuftags, _params);
+            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;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        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 rbfv1calcbuf(rbfv1model s,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            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, _params), "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] = s.v[i,mxnx];
+                for(j=0; j<=s.nx-1; j++)
+                {
+                    y[i] = y[i]+s.v[i,j]*x[j];
+                }
+            }
+            if( s.nc==0 )
+            {
+                return;
+            }
+            apserv.rvectorsetlengthatleast(ref s.calcbufxcx, mxnx, _params);
+            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, _params);
+            nearestneighbor.kdtreequeryresultsx(s.tree, ref s.calcbufx, _params);
+            nearestneighbor.kdtreequeryresultstags(s.tree, ref s.calcbuftags, _params);
+            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;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF model at the given point, using
+        external  buffer  object  (internal  temporaries  of  RBF  model  are  not
+        modified).
+
+        This function allows to use same RBF model object  in  different  threads,
+        assuming  that  different   threads  use  different  instances  of  buffer
+        structure.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, may be shared between different threads
+            Buf     -   buffer object created for this particular instance of  RBF
+                        model with rbfcreatecalcbuffer().
+            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 rbfv1tscalcbuf(rbfv1model s,
+            rbfv1calcbuffer buf,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            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, _params), "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] = s.v[i,mxnx];
+                for(j=0; j<=s.nx-1; j++)
+                {
+                    y[i] = y[i]+s.v[i,j]*x[j];
+                }
+            }
+            if( s.nc==0 )
+            {
+                return;
+            }
+            apserv.rvectorsetlengthatleast(ref buf.calcbufxcx, mxnx, _params);
+            for(i=0; i<=mxnx-1; i++)
+            {
+                buf.calcbufxcx[i] = 0.0;
+            }
+            for(i=0; i<=s.nx-1; i++)
+            {
+                buf.calcbufxcx[i] = x[i];
+            }
+            lx = nearestneighbor.kdtreetsqueryrnn(s.tree, buf.requestbuffer, buf.calcbufxcx, s.rmax*rbffarradius, true, _params);
+            nearestneighbor.kdtreetsqueryresultsx(s.tree, buf.requestbuffer, ref buf.calcbufx, _params);
+            nearestneighbor.kdtreetsqueryresultstags(s.tree, buf.requestbuffer, ref buf.calcbuftags, _params);
+            for(i=0; i<=s.ny-1; i++)
+            {
+                for(j=0; j<=lx-1; j++)
+                {
+                    tg = buf.calcbuftags[j];
+                    rcur = s.wr[tg,0];
+                    bf = Math.Exp(-((math.sqr(buf.calcbufxcx[0]-buf.calcbufx[j,0])+math.sqr(buf.calcbufxcx[1]-buf.calcbufx[j,1])+math.sqr(buf.calcbufxcx[2]-buf.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;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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.
+                        
+        NOTE: as a special exception, this function supports unordered  arrays  X0
+              and X1. However, future versions may be  more  efficient  for  X0/X1
+              ordered by ascending.
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfv1gridcalc2(rbfv1model s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            ref double[,] y,
+            alglib.xparams _params)
+        {
+            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, _params), "RBFGridCalc2: X0 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x1, n1, _params), "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, _params);
+            cpx1 = new double[n1];
+            for(i=0; i<=n1-1; i++)
+            {
+                cpx1[i] = x1[i];
+            }
+            tsort.tagsort(ref cpx1, n1, ref p11, ref p2, _params);
+            
+            //
+            //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, _params);
+                    i01 = tsort.upperbound(cpx0, n0, s.xc[i,0]+rlimit, _params);
+                    i10 = tsort.lowerbound(cpx1, n1, s.xc[i,1]-rlimit, _params);
+                    i11 = tsort.upperbound(cpx1, n1, s.xc[i,1]+rlimit, _params);
+                    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];
+                }
+            }
+        }
+
+
+        public static void rbfv1gridcalc3vrec(rbfv1model s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            double[] x2,
+            int n2,
+            int[] blocks0,
+            int block0a,
+            int block0b,
+            int[] blocks1,
+            int block1a,
+            int block1b,
+            int[] blocks2,
+            int block2a,
+            int block2b,
+            bool[] flagy,
+            bool sparsey,
+            double searchradius,
+            double avgfuncpernode,
+            alglib.smp.shared_pool bufpool,
+            double[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int t = 0;
+            int l = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int ic = 0;
+            gridcalc3v1buf pbuf = null;
+            int flag12dim1 = 0;
+            int flag12dim2 = 0;
+            double problemcost = 0;
+            int maxbs = 0;
+            int nx = 0;
+            int ny = 0;
+            double v = 0;
+            int kc = 0;
+            int tg = 0;
+            double rcur = 0;
+            double rcur2 = 0;
+            double basisfuncval = 0;
+            int dstoffs = 0;
+            int srcoffs = 0;
+            int ubnd = 0;
+            double w0 = 0;
+            double w1 = 0;
+            double w2 = 0;
+            bool allnodes = new bool();
+            bool somenodes = new bool();
+
+            nx = s.nx;
+            ny = s.ny;
+            
+            //
+            // Try to split large problem
+            //
+            problemcost = (s.nl+1)*s.ny*2*(avgfuncpernode+1);
+            problemcost = problemcost*(blocks0[block0b]-blocks0[block0a]);
+            problemcost = problemcost*(blocks1[block1b]-blocks1[block1a]);
+            problemcost = problemcost*(blocks2[block2b]-blocks2[block2a]);
+            maxbs = 0;
+            maxbs = Math.Max(maxbs, block0b-block0a);
+            maxbs = Math.Max(maxbs, block1b-block1a);
+            maxbs = Math.Max(maxbs, block2b-block2a);
+            if( (double)(problemcost)>=(double)(minbasecasecost) && maxbs>=2 )
+            {
+                if( block0b-block0a==maxbs )
+                {
+                    rbfv1gridcalc3vrec(s, x0, n0, x1, n1, x2, n2, blocks0, block0a, block0a+maxbs/2, blocks1, block1a, block1b, blocks2, block2a, block2b, flagy, sparsey, searchradius, avgfuncpernode, bufpool, y, _params);
+                    rbfv1gridcalc3vrec(s, x0, n0, x1, n1, x2, n2, blocks0, block0a+maxbs/2, block0b, blocks1, block1a, block1b, blocks2, block2a, block2b, flagy, sparsey, searchradius, avgfuncpernode, bufpool, y, _params);
+                    return;
+                }
+                if( block1b-block1a==maxbs )
+                {
+                    rbfv1gridcalc3vrec(s, x0, n0, x1, n1, x2, n2, blocks0, block0a, block0b, blocks1, block1a, block1a+maxbs/2, blocks2, block2a, block2b, flagy, sparsey, searchradius, avgfuncpernode, bufpool, y, _params);
+                    rbfv1gridcalc3vrec(s, x0, n0, x1, n1, x2, n2, blocks0, block0a, block0b, blocks1, block1a+maxbs/2, block1b, blocks2, block2a, block2b, flagy, sparsey, searchradius, avgfuncpernode, bufpool, y, _params);
+                    return;
+                }
+                if( block2b-block2a==maxbs )
+                {
+                    rbfv1gridcalc3vrec(s, x0, n0, x1, n1, x2, n2, blocks0, block0a, block0b, blocks1, block1a, block1b, blocks2, block2a, block2a+maxbs/2, flagy, sparsey, searchradius, avgfuncpernode, bufpool, y, _params);
+                    rbfv1gridcalc3vrec(s, x0, n0, x1, n1, x2, n2, blocks0, block0a, block0b, blocks1, block1a, block1b, blocks2, block2a+maxbs/2, block2b, flagy, sparsey, searchradius, avgfuncpernode, bufpool, y, _params);
+                    return;
+                }
+            }
+            
+            //
+            // Retrieve buffer object from pool (it will be returned later)
+            //
+            alglib.smp.ae_shared_pool_retrieve(bufpool, ref pbuf);
+            
+            //
+            // Calculate RBF model
+            //
+            for(i2=block2a; i2<=block2b-1; i2++)
+            {
+                for(i1=block1a; i1<=block1b-1; i1++)
+                {
+                    for(i0=block0a; i0<=block0b-1; i0++)
+                    {
+                        
+                        //
+                        // Analyze block - determine what elements are needed and what are not.
+                        //
+                        // After this block is done, two flag variables can be used:
+                        // * SomeNodes, which is True when there are at least one node which have
+                        //   to be calculated
+                        // * AllNodes, which is True when all nodes are required
+                        //
+                        somenodes = true;
+                        allnodes = true;
+                        flag12dim1 = blocks1[i1+1]-blocks1[i1];
+                        flag12dim2 = blocks2[i2+1]-blocks2[i2];
+                        if( sparsey )
+                        {
+                            
+                            //
+                            // Use FlagY to determine what is required.
+                            //
+                            apserv.bvectorsetlengthatleast(ref pbuf.flag0, n0, _params);
+                            apserv.bvectorsetlengthatleast(ref pbuf.flag1, n1, _params);
+                            apserv.bvectorsetlengthatleast(ref pbuf.flag2, n2, _params);
+                            apserv.bvectorsetlengthatleast(ref pbuf.flag12, flag12dim1*flag12dim2, _params);
+                            for(i=blocks0[i0]; i<=blocks0[i0+1]-1; i++)
+                            {
+                                pbuf.flag0[i] = false;
+                            }
+                            for(j=blocks1[i1]; j<=blocks1[i1+1]-1; j++)
+                            {
+                                pbuf.flag1[j] = false;
+                            }
+                            for(k=blocks2[i2]; k<=blocks2[i2+1]-1; k++)
+                            {
+                                pbuf.flag2[k] = false;
+                            }
+                            for(i=0; i<=flag12dim1*flag12dim2-1; i++)
+                            {
+                                pbuf.flag12[i] = false;
+                            }
+                            somenodes = false;
+                            allnodes = true;
+                            for(k=blocks2[i2]; k<=blocks2[i2+1]-1; k++)
+                            {
+                                for(j=blocks1[i1]; j<=blocks1[i1+1]-1; j++)
+                                {
+                                    dstoffs = j-blocks1[i1]+flag12dim1*(k-blocks2[i2]);
+                                    srcoffs = j*n0+k*n0*n1;
+                                    for(i=blocks0[i0]; i<=blocks0[i0+1]-1; i++)
+                                    {
+                                        if( flagy[srcoffs+i] )
+                                        {
+                                            pbuf.flag0[i] = true;
+                                            pbuf.flag1[j] = true;
+                                            pbuf.flag2[k] = true;
+                                            pbuf.flag12[dstoffs] = true;
+                                            somenodes = true;
+                                        }
+                                        else
+                                        {
+                                            allnodes = false;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        
+                        //
+                        // Skip block if it is completely empty.
+                        //
+                        if( !somenodes )
+                        {
+                            continue;
+                        }
+                        
+                        //
+                        // compute linear term for block (I0,I1,I2)
+                        //
+                        for(k=blocks2[i2]; k<=blocks2[i2+1]-1; k++)
+                        {
+                            for(j=blocks1[i1]; j<=blocks1[i1+1]-1; j++)
+                            {
+                                
+                                //
+                                // do we need this micro-row?
+                                //
+                                if( !allnodes && !pbuf.flag12[j-blocks1[i1]+flag12dim1*(k-blocks2[i2])] )
+                                {
+                                    continue;
+                                }
+                                
+                                //
+                                // Compute linear term
+                                //
+                                for(i=blocks0[i0]; i<=blocks0[i0+1]-1; i++)
+                                {
+                                    pbuf.tx[0] = x0[i];
+                                    pbuf.tx[1] = x1[j];
+                                    pbuf.tx[2] = x2[k];
+                                    for(l=0; l<=s.ny-1; l++)
+                                    {
+                                        v = s.v[l,mxnx];
+                                        for(t=0; t<=nx-1; t++)
+                                        {
+                                            v = v+s.v[l,t]*pbuf.tx[t];
+                                        }
+                                        y[l+ny*(i+j*n0+k*n0*n1)] = v;
+                                    }
+                                }
+                            }
+                        }
+                        
+                        //
+                        // compute RBF term for block (I0,I1,I2)
+                        //
+                        pbuf.tx[0] = 0.5*(x0[blocks0[i0]]+x0[blocks0[i0+1]-1]);
+                        pbuf.tx[1] = 0.5*(x1[blocks1[i1]]+x1[blocks1[i1+1]-1]);
+                        pbuf.tx[2] = 0.5*(x2[blocks2[i2]]+x2[blocks2[i2+1]-1]);
+                        kc = nearestneighbor.kdtreetsqueryrnn(s.tree, pbuf.requestbuf, pbuf.tx, searchradius, true, _params);
+                        nearestneighbor.kdtreetsqueryresultsx(s.tree, pbuf.requestbuf, ref pbuf.calcbufx, _params);
+                        nearestneighbor.kdtreetsqueryresultstags(s.tree, pbuf.requestbuf, ref pbuf.calcbuftags, _params);
+                        for(ic=0; ic<=kc-1; ic++)
+                        {
+                            pbuf.cx[0] = pbuf.calcbufx[ic,0];
+                            pbuf.cx[1] = pbuf.calcbufx[ic,1];
+                            pbuf.cx[2] = pbuf.calcbufx[ic,2];
+                            tg = pbuf.calcbuftags[ic];
+                            rcur = s.wr[tg,0];
+                            rcur2 = rcur*rcur;
+                            for(i=blocks0[i0]; i<=blocks0[i0+1]-1; i++)
+                            {
+                                if( allnodes || pbuf.flag0[i] )
+                                {
+                                    pbuf.expbuf0[i] = Math.Exp(-(math.sqr(x0[i]-pbuf.cx[0])/rcur2));
+                                }
+                                else
+                                {
+                                    pbuf.expbuf0[i] = 0.0;
+                                }
+                            }
+                            for(j=blocks1[i1]; j<=blocks1[i1+1]-1; j++)
+                            {
+                                if( allnodes || pbuf.flag1[j] )
+                                {
+                                    pbuf.expbuf1[j] = Math.Exp(-(math.sqr(x1[j]-pbuf.cx[1])/rcur2));
+                                }
+                                else
+                                {
+                                    pbuf.expbuf1[j] = 0.0;
+                                }
+                            }
+                            for(k=blocks2[i2]; k<=blocks2[i2+1]-1; k++)
+                            {
+                                if( allnodes || pbuf.flag2[k] )
+                                {
+                                    pbuf.expbuf2[k] = Math.Exp(-(math.sqr(x2[k]-pbuf.cx[2])/rcur2));
+                                }
+                                else
+                                {
+                                    pbuf.expbuf2[k] = 0.0;
+                                }
+                            }
+                            for(t=0; t<=s.nl-1; t++)
+                            {
+                                
+                                //
+                                // Calculate
+                                //
+                                for(k=blocks2[i2]; k<=blocks2[i2+1]-1; k++)
+                                {
+                                    for(j=blocks1[i1]; j<=blocks1[i1+1]-1; j++)
+                                    {
+                                        
+                                        //
+                                        // do we need this micro-row?
+                                        //
+                                        if( !allnodes && !pbuf.flag12[j-blocks1[i1]+flag12dim1*(k-blocks2[i2])] )
+                                        {
+                                            continue;
+                                        }
+                                        
+                                        //
+                                        // Prepare local variables
+                                        //
+                                        dstoffs = ny*(blocks0[i0]+j*n0+k*n0*n1);
+                                        v = pbuf.expbuf1[j]*pbuf.expbuf2[k];
+                                        
+                                        //
+                                        // Optimized for NY=1
+                                        //
+                                        if( s.ny==1 )
+                                        {
+                                            w0 = s.wr[tg,1+t*s.ny+0];
+                                            ubnd = blocks0[i0+1]-1;
+                                            for(i=blocks0[i0]; i<=ubnd; i++)
+                                            {
+                                                basisfuncval = pbuf.expbuf0[i]*v;
+                                                y[dstoffs] = y[dstoffs]+basisfuncval*w0;
+                                                dstoffs = dstoffs+1;
+                                            }
+                                            continue;
+                                        }
+                                        
+                                        //
+                                        // Optimized for NY=2
+                                        //
+                                        if( s.ny==2 )
+                                        {
+                                            w0 = s.wr[tg,1+t*s.ny+0];
+                                            w1 = s.wr[tg,1+t*s.ny+1];
+                                            ubnd = blocks0[i0+1]-1;
+                                            for(i=blocks0[i0]; i<=ubnd; i++)
+                                            {
+                                                basisfuncval = pbuf.expbuf0[i]*v;
+                                                y[dstoffs+0] = y[dstoffs+0]+basisfuncval*w0;
+                                                y[dstoffs+1] = y[dstoffs+1]+basisfuncval*w1;
+                                                dstoffs = dstoffs+2;
+                                            }
+                                            continue;
+                                        }
+                                        
+                                        //
+                                        // Optimized for NY=3
+                                        //
+                                        if( s.ny==3 )
+                                        {
+                                            w0 = s.wr[tg,1+t*s.ny+0];
+                                            w1 = s.wr[tg,1+t*s.ny+1];
+                                            w2 = s.wr[tg,1+t*s.ny+2];
+                                            ubnd = blocks0[i0+1]-1;
+                                            for(i=blocks0[i0]; i<=ubnd; i++)
+                                            {
+                                                basisfuncval = pbuf.expbuf0[i]*v;
+                                                y[dstoffs+0] = y[dstoffs+0]+basisfuncval*w0;
+                                                y[dstoffs+1] = y[dstoffs+1]+basisfuncval*w1;
+                                                y[dstoffs+2] = y[dstoffs+2]+basisfuncval*w2;
+                                                dstoffs = dstoffs+3;
+                                            }
+                                            continue;
+                                        }
+                                        
+                                        //
+                                        // General case
+                                        //
+                                        for(i=blocks0[i0]; i<=blocks0[i0+1]-1; i++)
+                                        {
+                                            basisfuncval = pbuf.expbuf0[i]*v;
+                                            for(l=0; l<=s.ny-1; l++)
+                                            {
+                                                y[l+dstoffs] = y[l+dstoffs]+basisfuncval*s.wr[tg,1+t*s.ny+l];
+                                            }
+                                            dstoffs = dstoffs+ny;
+                                        }
+                                    }
+                                }
+                                
+                                //
+                                // Update basis functions
+                                //
+                                if( t!=s.nl-1 )
+                                {
+                                    ubnd = blocks0[i0+1]-1;
+                                    for(i=blocks0[i0]; i<=ubnd; i++)
+                                    {
+                                        if( allnodes || pbuf.flag0[i] )
+                                        {
+                                            v = pbuf.expbuf0[i]*pbuf.expbuf0[i];
+                                            pbuf.expbuf0[i] = v*v;
+                                        }
+                                    }
+                                    ubnd = blocks1[i1+1]-1;
+                                    for(j=blocks1[i1]; j<=ubnd; j++)
+                                    {
+                                        if( allnodes || pbuf.flag1[j] )
+                                        {
+                                            v = pbuf.expbuf1[j]*pbuf.expbuf1[j];
+                                            pbuf.expbuf1[j] = v*v;
+                                        }
+                                    }
+                                    ubnd = blocks2[i2+1]-1;
+                                    for(k=blocks2[i2]; k<=ubnd; k++)
+                                    {
+                                        if( allnodes || pbuf.flag2[k] )
+                                        {
+                                            v = pbuf.expbuf2[k]*pbuf.expbuf2[k];
+                                            pbuf.expbuf2[k] = v*v;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Recycle buffer object back to pool
+            //
+            alglib.smp.ae_shared_pool_recycle(bufpool, ref pbuf);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_rbfv1gridcalc3vrec(rbfv1model s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            double[] x2,
+            int n2,
+            int[] blocks0,
+            int block0a,
+            int block0b,
+            int[] blocks1,
+            int block1a,
+            int block1b,
+            int[] blocks2,
+            int block2a,
+            int block2b,
+            bool[] flagy,
+            bool sparsey,
+            double searchradius,
+            double avgfuncpernode,
+            alglib.smp.shared_pool bufpool,
+            double[] y, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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 rbfv1unpack(rbfv1model s,
+            ref int nx,
+            ref int ny,
+            ref double[,] xwr,
+            ref int nc,
+            ref double[,] v,
+            alglib.xparams _params)
+        {
+            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;
+                    }
+                }
+            }
+        }
+
+
+        private static bool rbfv1buildlinearmodel(double[,] x,
+            ref double[,] y,
+            int n,
+            int ny,
+            int modeltype,
+            ref double[,] v,
+            alglib.xparams _params)
+        {
+            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, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                nearestneighbor.kdtreequeryresultstags(centerstree, ref centerstags, _params);
+                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, _params);
+                nearpointscnt[i] = nearestneighbor.kdtreequeryrnn(pointstree, xcx, (r[i]+maxrad)*rbfnearradius, true, _params)-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, _params);
+                
+                //
+                // 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, _params);
+            sparse.sparsecreate(nc, nc, snnz, sps, _params);
+            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, _params);
+                alglib.ap.assert(tmpi==centerscnt, "BuildRBFModelLSQR: internal error");
+                nearestneighbor.kdtreequeryresultsx(centerstree, ref cx, _params);
+                nearestneighbor.kdtreequeryresultstags(centerstree, ref centerstags, _params);
+                
+                //
+                // 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, _params);
+                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, _params);
+                    alglib.ap.assert(tmpi==skipnearpointscnt[i]+nearpointscnt[i], "BuildRBFModelLSQR: internal error");
+                    nearestneighbor.kdtreequeryresultsx(pointstree, ref xx, _params);
+                    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, _params);
+                    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], _params);
+                    }
+                }
+                
+                //
+                // second KDTree request for points
+                //
+                pointscnt = farpointscnt[i];
+                tmpi = nearestneighbor.kdtreequeryknn(pointstree, xcx, pointscnt, true, _params);
+                alglib.ap.assert(tmpi==pointscnt, "BuildRBFModelLSQR: internal error");
+                nearestneighbor.kdtreequeryresultsx(pointstree, ref xx, _params);
+                nearestneighbor.kdtreequeryresultstags(pointstree, ref pointstags, _params);
+                
+                //
+                //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, _params);
+                }
+            }
+            sparse.sparseconverttocrs(spg, _params);
+            sparse.sparseconverttocrs(sps, _params);
+            
+            //
+            // solve by LSQR method
+            //
+            tmpy = new double[n];
+            tc = new double[nc];
+            w = new double[nc, ny];
+            linlsqr.linlsqrcreate(n, nc, state, _params);
+            linlsqr.linlsqrsetcond(state, epsort, epserr, maxits, _params);
+            for(i=0; i<=ny-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    tmpy[j] = y[j,i];
+                }
+                linlsqr.linlsqrsolvesparse(state, spg, tmpy, _params);
+                linlsqr.linlsqrresults(state, ref c, lsqrrep, _params);
+                if( lsqrrep.terminationtype<=0 )
+                {
+                    info = -4;
+                    return;
+                }
+                sparse.sparsemtv(sps, c, ref tc, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            omega = new double[n];
+            tmpy = new double[n];
+            w = new double[nc, ny];
+            info = -1;
+            iterationscount = 0;
+            nmv = 0;
+            linlsqr.linlsqrcreate(n, n, state, _params);
+            linlsqr.linlsqrsetcond(state, epsort, epserr, maxits, _params);
+            linlsqr.linlsqrsetlambdai(state, 1.0E-6, _params);
+            
+            //
+            // 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, _params);
+            }
+            for(layer=0; layer<=nlayers-1; layer++)
+            {
+                
+                //
+                // Fill sparse matrix, calculate norm(A)
+                //
+                anorm = 0.0;
+                sparse.sparsecreate(n, n, annz, spa, _params);
+                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, _params);
+                    nearestneighbor.kdtreequeryresultsx(centerstree, ref cx, _params);
+                    nearestneighbor.kdtreequeryresultstags(centerstree, ref centerstags, _params);
+                    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, _params);
+                        anorm = anorm+math.sqr(v);
+                    }
+                }
+                anorm = Math.Sqrt(anorm);
+                sparse.sparseconverttocrs(spa, _params);
+                
+                //
+                // 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, _params);
+                    linlsqr.linlsqrsolvesparse(state, spa, tmpy, _params);
+                    linlsqr.linlsqrresults(state, ref omega, lsqrrep, _params);
+                    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, _params);
+                        nearestneighbor.kdtreequeryresultsx(centerstree, ref cx, _params);
+                        nearestneighbor.kdtreequeryresultstags(centerstree, ref centerstags, _params);
+                        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 rbf
+    {
+        /*************************************************************************
+        Buffer object which is used to perform nearest neighbor  requests  in  the
+        multithreaded mode (multiple threads working with same KD-tree object).
+
+        This object should be created with KDTreeCreateBuffer().
+        *************************************************************************/
+        public class rbfcalcbuffer : apobject
+        {
+            public int modelversion;
+            public rbfv1.rbfv1calcbuffer bufv1;
+            public rbfv2.rbfv2calcbuffer bufv2;
+            public rbfcalcbuffer()
+            {
+                init();
+            }
+            public override void init()
+            {
+                bufv1 = new rbfv1.rbfv1calcbuffer();
+                bufv2 = new rbfv2.rbfv2calcbuffer();
+            }
+            public override alglib.apobject make_copy()
+            {
+                rbfcalcbuffer _result = new rbfcalcbuffer();
+                _result.modelversion = modelversion;
+                _result.bufv1 = (rbfv1.rbfv1calcbuffer)bufv1.make_copy();
+                _result.bufv2 = (rbfv2.rbfv2calcbuffer)bufv2.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        RBF model.
+
+        Never try to directly work with fields of this object - always use  ALGLIB
+        functions to use this object.
+        *************************************************************************/
+        public class rbfmodel : apobject
+        {
+            public int nx;
+            public int ny;
+            public int modelversion;
+            public rbfv1.rbfv1model model1;
+            public rbfv2.rbfv2model model2;
+            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 int nnmaxits;
+            public int n;
+            public double[,] x;
+            public double[,] y;
+            public bool hasscale;
+            public double[] s;
+            public int progress10000;
+            public bool terminationrequest;
+            public rbfmodel()
+            {
+                init();
+            }
+            public override void init()
+            {
+                model1 = new rbfv1.rbfv1model();
+                model2 = new rbfv2.rbfv2model();
+                x = new double[0,0];
+                y = new double[0,0];
+                s = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                rbfmodel _result = new rbfmodel();
+                _result.nx = nx;
+                _result.ny = ny;
+                _result.modelversion = modelversion;
+                _result.model1 = (rbfv1.rbfv1model)model1.make_copy();
+                _result.model2 = (rbfv2.rbfv2model)model2.make_copy();
+                _result.lambdav = lambdav;
+                _result.radvalue = radvalue;
+                _result.radzvalue = radzvalue;
+                _result.nlayers = nlayers;
+                _result.aterm = aterm;
+                _result.algorithmtype = algorithmtype;
+                _result.epsort = epsort;
+                _result.epserr = epserr;
+                _result.maxits = maxits;
+                _result.nnmaxits = nnmaxits;
+                _result.n = n;
+                _result.x = (double[,])x.Clone();
+                _result.y = (double[,])y.Clone();
+                _result.hasscale = hasscale;
+                _result.s = (double[])s.Clone();
+                _result.progress10000 = progress10000;
+                _result.terminationrequest = terminationrequest;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        RBF solution report:
+        * TerminationType   -   termination type, positive values - success,
+                                non-positive - failure.
+                                
+        Fields which are set by modern RBF solvers (hierarchical):
+        * RMSError          -   root-mean-square error; NAN for old solvers (ML, QNN)
+        * MaxError          -   maximum error; NAN for old solvers (ML, QNN)
+        *************************************************************************/
+        public class rbfreport : apobject
+        {
+            public double rmserror;
+            public double maxerror;
+            public int arows;
+            public int acols;
+            public int annz;
+            public int iterationscount;
+            public int nmv;
+            public int terminationtype;
+            public rbfreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                rbfreport _result = new rbfreport();
+                _result.rmserror = rmserror;
+                _result.maxerror = maxerror;
+                _result.arows = arows;
+                _result.acols = acols;
+                _result.annz = annz;
+                _result.iterationscount = iterationscount;
+                _result.nmv = nmv;
+                _result.terminationtype = terminationtype;
+                return _result;
+            }
+        };
+
+
+
+
+        public const double eps = 1.0E-6;
+        public const double rbffarradius = 6;
+        public const int rbffirstversion = 0;
+        public const int rbfversion2 = 2;
+
+
+        /*************************************************************************
+        This function creates RBF  model  for  a  scalar (NY=1)  or  vector (NY>1)
+        function in a NX-dimensional space (NX>=1).
+
+        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) or rbfsetpointsandscales().
+        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 tweaks algorithm properties with  rbfsetalgohierarchical()  method
+           (or chooses one of the legacy algorithms - 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.
+           
+        IMPORTANT: we recommend you to use latest model construction  algorithm  -
+                   hierarchical RBFs, which is activated by rbfsetalgohierarchical()
+                   function. This algorithm is the fastest one, and  most  memory-
+                   efficient.
+                   However,  it  is  incompatible  with older versions  of  ALGLIB
+                   (pre-3.11). So, if you serialize hierarchical model,  you  will
+                   be unable to load it in pre-3.11 ALGLIB. Other model types (QNN
+                   and RBF-ML) are still backward-compatible.
+           
+        INPUT PARAMETERS:
+            NX      -   dimension of the space, NX>=1
+            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. Some additional memory
+                is allocated during model construction, but most of this memory is
+                freed after model coefficients  are  calculated.  Amount  of  this
+                additional memory depends on model  construction  algorithm  being
+                used.
+                
+        NOTE 2: prior to ALGLIB version 3.11, RBF models supported  only  NX=2  or
+                NX=3. Any  attempt  to  create  single-dimensional  or  more  than
+                3-dimensional RBF model resulted in exception.
+                
+                ALGLIB 3.11 supports any NX>0, but models created with  NX!=2  and
+                NX!=3 are incompatible with (a) older versions of ALGLIB, (b)  old
+                model construction algorithms (QNN or RBF-ML).
+                
+                So, if you create a model with NX=2 or NX=3,  then,  depending  on
+                specific  model construction algorithm being chosen, you will (QNN
+                and RBF-ML) or will not (HierarchicalRBF) get backward compatibility
+                with older versions of ALGLIB. You have a choice here.
+                
+                However, if you create a model with NX neither 2 nor 3,  you  have
+                no backward compatibility from the start, and you  are  forced  to
+                use hierarchical RBFs and ALGLIB 3.11 or later.
+
+          -- ALGLIB --
+             Copyright 13.12.2011, 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfcreate(int nx,
+            int ny,
+            rbfmodel s,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(nx>=1, "RBFCreate: NX<1");
+            alglib.ap.assert(ny>=1, "RBFCreate: NY<1");
+            s.nx = nx;
+            s.ny = ny;
+            rbfpreparenonserializablefields(s, _params);
+            
+            //
+            // Select default model version according to NX.
+            //
+            // The idea is that when we call this function with NX=2 or NX=3, backward
+            // compatible dummy (zero) V1 model is created, so serialization produces
+            // model which are compatible with pre-3.11 ALGLIB.
+            //
+            initializev1(nx, ny, s.model1, _params);
+            initializev2(nx, ny, s.model2, _params);
+            if( nx==2 || nx==3 )
+            {
+                s.modelversion = 1;
+            }
+            else
+            {
+                s.modelversion = 2;
+            }
+            
+            //
+            // Report fields
+            //
+            s.progress10000 = 0;
+            s.terminationrequest = false;
+        }
+
+
+        /*************************************************************************
+        This function creates buffer  structure  which  can  be  used  to  perform
+        parallel  RBF  model  evaluations  (with  one  RBF  model  instance  being
+        used from multiple threads, as long as  different  threads  use  different
+        instances of buffer).
+
+        This buffer object can be used with  rbftscalcbuf()  function  (here  "ts"
+        stands for "thread-safe", "buf" is a suffix which denotes  function  which
+        reuses previously allocated output space).
+
+        How to use it:
+        * create RBF model structure with rbfcreate()
+        * load data, tune parameters
+        * call rbfbuildmodel()
+        * call rbfcreatecalcbuffer(), once per thread working with RBF model  (you
+          should call this function only AFTER call to rbfbuildmodel(), see  below
+          for more information)
+        * call rbftscalcbuf() from different threads,  with  each  thread  working
+          with its own copy of buffer object.
+
+        INPUT PARAMETERS
+            S           -   RBF model
+
+        OUTPUT PARAMETERS
+            Buf         -   external buffer.
+            
+            
+        IMPORTANT: buffer object should be used only with  RBF model object  which
+                   was used to initialize buffer. Any attempt to use buffer   with
+                   different object is dangerous - you may  get  memory  violation
+                   error because sizes of internal arrays do not fit to dimensions
+                   of RBF structure.
+                   
+        IMPORTANT: you  should  call  this function only for model which was built
+                   with rbfbuildmodel() function, after successful  invocation  of
+                   rbfbuildmodel().  Sizes   of   some   internal  structures  are
+                   determined only after model is built, so buffer object  created
+                   before model  construction  stage  will  be  useless  (and  any
+                   attempt to use it will result in exception).
+
+          -- ALGLIB --
+             Copyright 02.04.2016 by Sergey Bochkanov
+        *************************************************************************/
+        public static void rbfcreatecalcbuffer(rbfmodel s,
+            rbfcalcbuffer buf,
+            alglib.xparams _params)
+        {
+            if( s.modelversion==1 )
+            {
+                buf.modelversion = 1;
+                rbfv1.rbfv1createcalcbuffer(s.model1, buf.bufv1, _params);
+                return;
+            }
+            if( s.modelversion==2 )
+            {
+                buf.modelversion = 2;
+                rbfv2.rbfv2createcalcbuffer(s.model2, buf.bufv2, _params);
+                return;
+            }
+            alglib.ap.assert(false, "RBFCreateCalcBuffer: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        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.
+
+        IMPORTANT: ALGLIB version 3.11 and later allows you to specify  a  set  of
+                   per-dimension scales. Interpolation radii are multiplied by the
+                   scale vector. It may be useful if you have mixed spatio-temporal
+                   data (say, a set of 3D slices recorded at different times).
+                   You should call rbfsetpointsandscales() function  to  use  this
+                   feature.
+
+        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 
+                        specified, 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: dataset added by this function is not saved during model serialization.
+              MODEL ITSELF is serialized, but data used to build it are not.
+              
+              So, if you 1) add dataset to  empty  RBF  model,  2)  serialize  and
+              unserialize it, then you will get an empty RBF model with no dataset
+              being attached.
+              
+              From the other side, if you call rbfbuildmodel() between (1) and (2),
+              then after (2) you will get your fully constructed RBF model  -  but
+              again with no dataset attached, so subsequent calls to rbfbuildmodel()
+              will produce empty model.
+              
+
+          -- ALGLIB --
+             Copyright 13.12.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetpoints(rbfmodel s,
+            double[,] xy,
+            int n,
+            alglib.xparams _params)
+        {
+            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");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, n, s.nx+s.ny, _params), "RBFSetPoints: XY contains infinite or NaN values!");
+            s.n = n;
+            s.hasscale = false;
+            s.x = new double[s.n, s.nx];
+            s.y = new double[s.n, s.ny];
+            for(i=0; i<=s.n-1; i++)
+            {
+                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 adds dataset and a vector of per-dimension scales.
+
+        It may be useful if you have mixed spatio-temporal data - say, a set of 3D
+        slices recorded at different times. Such data typically require  different
+        RBF radii for spatial and temporal dimensions. ALGLIB solves this  problem
+        by specifying single RBF radius, which is (optionally) multiplied  by  the
+        scale vector.
+
+        This function overrides results of the previous calls, i.e. multiple calls
+        of this function will result in only the last set being added.
+
+        IMPORTANT: only HierarchicalRBF algorithm can work with scaled points. So,
+                   using this function results in RBF models which can be used  in
+                   ALGLIB 3.11 or later. Previous versions of the library will  be
+                   unable  to unserialize models produced by HierarchicalRBF algo.
+                   
+                   Any attempt to use this function with RBF-ML or QNN  algorithms
+                   will result  in  -3  error  code   being   returned  (incorrect
+                   algorithm).
+
+        INPUT PARAMETERS:
+            R       -   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 
+                        specified, in  this  case  only leading [N,NX+NY] elements 
+                        will be used.
+            N       -   number of points in the dataset
+            S       -   array[NX], scale vector, S[i]>0.
+
+        After you've added dataset and (optionally) tuned algorithm  settings  you
+        should call rbfbuildmodel() in order to build a model for you.
+
+        NOTE: dataset added by this function is not saved during model serialization.
+              MODEL ITSELF is serialized, but data used to build it are not.
+              
+              So, if you 1) add dataset to  empty  RBF  model,  2)  serialize  and
+              unserialize it, then you will get an empty RBF model with no dataset
+              being attached.
+              
+              From the other side, if you call rbfbuildmodel() between (1) and (2),
+              then after (2) you will get your fully constructed RBF model  -  but
+              again with no dataset attached, so subsequent calls to rbfbuildmodel()
+              will produce empty model.
+              
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetpointsandscales(rbfmodel r,
+            double[,] xy,
+            int n,
+            double[] s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(n>0, "RBFSetPointsAndScales: N<0");
+            alglib.ap.assert(alglib.ap.rows(xy)>=n, "RBFSetPointsAndScales: Rows(XY)<N");
+            alglib.ap.assert(alglib.ap.cols(xy)>=r.nx+r.ny, "RBFSetPointsAndScales: Cols(XY)<NX+NY");
+            alglib.ap.assert(alglib.ap.len(s)>=r.nx, "RBFSetPointsAndScales: Length(S)<NX");
+            r.n = n;
+            r.hasscale = true;
+            r.x = new double[r.n, r.nx];
+            r.y = new double[r.n, r.ny];
+            for(i=0; i<=r.n-1; i++)
+            {
+                for(j=0; j<=r.nx-1; j++)
+                {
+                    r.x[i,j] = xy[i,j];
+                }
+                for(j=0; j<=r.ny-1; j++)
+                {
+                    r.y[i,j] = xy[i,j+r.nx];
+                }
+            }
+            r.s = new double[r.nx];
+            for(i=0; i<=r.nx-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(s[i]), "RBFSetPointsAndScales: S[i] is not finite number");
+                alglib.ap.assert((double)(s[i])>(double)(0), "RBFSetPointsAndScales: S[i]<=0");
+                r.s[i] = s[i];
+            }
+        }
+
+
+        /*************************************************************************
+        DEPRECATED:since version 3.11 ALGLIB includes new RBF  model  construction
+                   algorithm, Hierarchical  RBF.  This  algorithm  is  faster  and
+                   requires less memory than QNN and RBF-ML. It is especially good
+                   for large-scale interpolation problems. So, we recommend you to
+                   consider Hierarchical RBF as default option.
+                   
+        ==========================================================================
+
+        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.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(q), "RBFSetAlgoQNN: Q is infinite or NAN");
+            alglib.ap.assert((double)(q)>(double)(0), "RBFSetAlgoQNN: Q<=0");
+            alglib.ap.assert(math.isfinite(z), "RBFSetAlgoQNN: Z is infinite or NAN");
+            alglib.ap.assert((double)(z)>(double)(0), "RBFSetAlgoQNN: Z<=0");
+            s.radvalue = q;
+            s.radzvalue = z;
+            s.algorithmtype = 1;
+        }
+
+
+        /*************************************************************************
+        DEPRECATED:since version 3.11 ALGLIB includes new RBF  model  construction
+                   algorithm, Hierarchical  RBF.  This  algorithm  is  faster  and
+                   requires less memory than QNN and RBF-ML. It is especially good
+                   for large-scale interpolation problems. So, we recommend you to
+                   consider Hierarchical RBF as default option.
+                   
+        ==========================================================================
+
+        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.xparams _params)
+        {
+            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  RBF interpolation algorithm. ALGLIB supports several
+        RBF algorithms with different properties.
+
+        This  algorithm is called Hierarchical RBF. It  similar  to  its  previous
+        incarnation, RBF-ML, i.e.  it  also  builds  a  sequence  of  models  with
+        decreasing radii. However, it uses more economical way of  building  upper
+        layers (ones with large radii), which results in faster model construction
+        and evaluation, as well as smaller memory footprint during construction.
+
+        This algorithm has following important features:
+        * ability to handle millions of points
+        * controllable smoothing via nonlinearity penalization
+        * support for NX-dimensional models with NX=1 or NX>3 (unlike QNN or RBF-ML)
+        * support for specification of per-dimensional  radii  via  scale  vector,
+          which is set by means of rbfsetpointsandscales() function. This  feature
+          is useful if you solve  spatio-temporal  interpolation  problems,  where
+          different radii are required for spatial and temporal dimensions.
+
+        Running times are roughly proportional to:
+        * N*log(N)*NLayers - for model construction
+        * N*NLayers - for model evaluation
+        You may see that running time does not depend on search radius  or  points
+        density, just on number of layers in the hierarchy.
+
+        IMPORTANT: this model construction algorithm was introduced in ALGLIB 3.11
+                   and  produces  models  which  are  INCOMPATIBLE  with  previous
+                   versions of ALGLIB. You can  not  unserialize  models  produced
+                   with this function in ALGLIB 3.10 or earlier.
+
+        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.
+            LambdaNS-   >=0, nonlinearity penalty coefficient, negative values are
+                        not allowed. This parameter adds controllable smoothing to
+                        the problem, which may reduce noise. Specification of non-
+                        zero lambda means that in addition to fitting error solver
+                        will  also  minimize   LambdaNS*|S''(x)|^2  (appropriately
+                        generalized to multiple dimensions.
+                        
+                        Specification of exactly zero value means that no  penalty
+                        is added  (we  do  not  even  evaluate  matrix  of  second
+                        derivatives which is necessary for smoothing).
+                        
+                        Calculation of nonlinearity penalty is costly - it results
+                        in  several-fold  increase  of  model  construction  time.
+                        Evaluation time remains the same.
+                        
+                        Optimal  lambda  is  problem-dependent and requires  trial
+                        and  error.  Good  value to  start  from  is  1e-5...1e-6,
+                        which corresponds to slightly noticeable smoothing  of the
+                        function.  Value  1e-2  usually  means  that  quite  heavy
+                        smoothing is applied.
+
+        TUNING ALGORITHM
+
+        In order to use this algorithm you have to choose three parameters:
+        * initial radius RBase
+        * number of layers in the model NLayers
+        * penalty coefficient LambdaNS
+
+        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 (or specify
+        non-zero LambdaNS).
+
+        TYPICAL ERRORS
+
+        1. 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.
+
+        2. 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.
+
+          -- ALGLIB --
+             Copyright 20.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetalgohierarchical(rbfmodel s,
+            double rbase,
+            int nlayers,
+            double lambdans,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(rbase), "RBFSetAlgoHierarchical: RBase is infinite or NaN");
+            alglib.ap.assert((double)(rbase)>(double)(0), "RBFSetAlgoHierarchical: RBase<=0");
+            alglib.ap.assert(nlayers>=0, "RBFSetAlgoHierarchical: NLayers<0");
+            alglib.ap.assert(math.isfinite(lambdans) && (double)(lambdans)>=(double)(0), "RBFSetAlgoHierarchical: LambdaNS<0 or infinite");
+            s.radvalue = rbase;
+            s.nlayers = nlayers;
+            s.algorithmtype = 3;
+            s.lambdav = lambdans;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            s.aterm = 3;
+        }
+
+
+        /*************************************************************************
+        This function sets basis function type, which can be:
+        * 0 for classic Gaussian
+        * 1 for fast and compact bell-like basis function, which  becomes  exactly
+          zero at distance equal to 3*R (default option).
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+            BF      -   basis function type:
+                        * 0 - classic Gaussian
+                        * 1 - fast and compact one
+
+          -- ALGLIB --
+             Copyright 01.02.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetv2bf(rbfmodel s,
+            int bf,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(bf==0 || bf==1, "RBFSetV2Its: BF<>0 and BF<>1");
+            s.model2.basisfunction = bf;
+        }
+
+
+        /*************************************************************************
+        This function sets stopping criteria of the underlying linear  solver  for
+        hierarchical (version 2) RBF constructor.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+            MaxIts  -   this criterion will stop algorithm after MaxIts iterations.
+                        Typically a few hundreds iterations is required,  with 400
+                        being a good default value to start experimentation.
+                        Zero value means that default value will be selected.
+
+          -- ALGLIB --
+             Copyright 01.02.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetv2its(rbfmodel s,
+            int maxits,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(maxits>=0, "RBFSetV2Its: MaxIts is negative");
+            s.model2.maxits = maxits;
+        }
+
+
+        /*************************************************************************
+        This function sets support radius parameter  of  hierarchical  (version 2)
+        RBF constructor.
+
+        Hierarchical RBF model achieves great speed-up  by removing from the model
+        excessive (too dense) nodes. Say, if you have RBF radius equal to 1 meter,
+        and two nodes are just 1 millimeter apart, you  may  remove  one  of  them
+        without reducing model quality.
+
+        Support radius parameter is used to justify which points need removal, and
+        which do not. If two points are less than  SUPPORT_R*CUR_RADIUS  units  of
+        distance apart, one of them is removed from the model. The larger  support
+        radius  is, the faster model  construction  AND  evaluation are.  However,
+        too large values result in "bumpy" models.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, initialized by RBFCreate() call
+            R       -   support radius coefficient, >=0.
+                        Recommended values are [0.1,0.4] range, with 0.1 being
+                        default value.
+
+          -- ALGLIB --
+             Copyright 01.02.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfsetv2supportr(rbfmodel s,
+            double r,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(r), "RBFSetV2SupportR: R is not finite");
+            alglib.ap.assert((double)(r)>=(double)(0), "RBFSetV2SupportR: R<0");
+            s.model2.supportr = r;
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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;  only  QNN  returns  this
+                                 error   code, other  algorithms  can  handle non-
+                                 distinct nodes.
+                          * -4 - nonconvergence of the internal SVD solver
+                          * -3   incorrect model construction algorithm was chosen:
+                                 QNN or RBF-ML, combined with one of the incompatible
+                                 features - NX=1 or NX>3; points with per-dimension
+                                 scales.
+                          *  1 - successful termination
+                          *  8 - a termination request was submitted via
+                                 rbfrequesttermination() function.
+                        
+                        Fields which are set only by modern RBF solvers (hierarchical
+                        or nonnegative; older solvers like QNN and ML initialize these
+                        fields by NANs):
+                        * rep.rmserror - root-mean-square error at nodes
+                        * rep.maxerror - maximum error at nodes
+                        
+                        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,
+            alglib.xparams _params)
+        {
+            rbfv1.rbfv1report rep1 = new rbfv1.rbfv1report();
+            rbfv2.rbfv2report rep2 = new rbfv2.rbfv2report();
+            double[,] x3 = new double[0,0];
+            double[] scalevec = new double[0];
+            int i = 0;
+            int curalgorithmtype = 0;
+
+            
+            //
+            // Clean fields prior to processing
+            //
+            clearreportfields(rep, _params);
+            s.progress10000 = 0;
+            s.terminationrequest = false;
+            
+            //
+            // Autoselect algorithm
+            //
+            if( s.algorithmtype==0 )
+            {
+                if( (s.nx<2 || s.nx>3) || s.hasscale )
+                {
+                    curalgorithmtype = 3;
+                }
+                else
+                {
+                    curalgorithmtype = 1;
+                }
+            }
+            else
+            {
+                curalgorithmtype = s.algorithmtype;
+            }
+            
+            //
+            // Algorithms which generate V1 models
+            //
+            if( curalgorithmtype==1 || curalgorithmtype==2 )
+            {
+                
+                //
+                // Perform compatibility checks
+                //
+                if( (s.nx<2 || s.nx>3) || s.hasscale )
+                {
+                    rep.terminationtype = -3;
+                    return;
+                }
+                
+                //
+                // Try to build model.
+                //
+                // NOTE: due to historical reasons RBFV1BuildModel() accepts points
+                //       cast to 3-dimensional space, even if they are really 2-dimensional.
+                //       So, for 2D data we have to explicitly convert them to 3D.
+                //
+                if( s.nx==2 )
+                {
+                    
+                    //
+                    // Convert data to 3D
+                    //
+                    apserv.rmatrixsetlengthatleast(ref x3, s.n, 3, _params);
+                    for(i=0; i<=s.n-1; i++)
+                    {
+                        x3[i,0] = s.x[i,0];
+                        x3[i,1] = s.x[i,1];
+                        x3[i,2] = 0;
+                    }
+                    rbfv1.rbfv1buildmodel(x3, s.y, s.n, s.aterm, curalgorithmtype, s.nlayers, s.radvalue, s.radzvalue, s.lambdav, s.epsort, s.epserr, s.maxits, s.model1, rep1, _params);
+                }
+                else
+                {
+                    
+                    //
+                    // Work with raw data
+                    //
+                    rbfv1.rbfv1buildmodel(s.x, s.y, s.n, s.aterm, curalgorithmtype, s.nlayers, s.radvalue, s.radzvalue, s.lambdav, s.epsort, s.epserr, s.maxits, s.model1, rep1, _params);
+                }
+                s.modelversion = 1;
+                
+                //
+                // Convert report fields
+                //
+                rep.arows = rep1.arows;
+                rep.acols = rep1.acols;
+                rep.annz = rep1.annz;
+                rep.iterationscount = rep1.iterationscount;
+                rep.nmv = rep1.nmv;
+                rep.terminationtype = rep1.terminationtype;
+                
+                //
+                // Done
+                //
+                return;
+            }
+            
+            //
+            // Algorithms which generate V2 models
+            //
+            if( curalgorithmtype==3 )
+            {
+                
+                //
+                // Prepare scale vector - use unit values or user supplied ones
+                //
+                scalevec = new double[s.nx];
+                for(i=0; i<=s.nx-1; i++)
+                {
+                    if( s.hasscale )
+                    {
+                        scalevec[i] = s.s[i];
+                    }
+                    else
+                    {
+                        scalevec[i] = 1;
+                    }
+                }
+                
+                //
+                // Build model
+                //
+                rbfv2.rbfv2buildhierarchical(s.x, s.y, s.n, scalevec, s.aterm, s.nlayers, s.radvalue, s.lambdav, s.model2, ref s.progress10000, ref s.terminationrequest, rep2, _params);
+                s.modelversion = 2;
+                
+                //
+                // Convert report fields
+                //
+                rep.terminationtype = rep2.terminationtype;
+                rep.rmserror = rep2.rmserror;
+                rep.maxerror = rep2.maxerror;
+                
+                //
+                // Done
+                //
+                return;
+            }
+            
+            //
+            // Critical error
+            //
+            alglib.ap.assert(false, "RBFBuildModel: integrity check failure");
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF model in the given point.
+
+        IMPORTANT: this function works only with modern  (hierarchical)  RBFs.  It 
+                   can not be used with legacy (version 1) RBFs because older  RBF
+                   code does not support 1-dimensional models.
+
+        This function should be used when we have NY=1 (scalar function) and  NX=1
+        (1-dimensional space). If you have 3-dimensional space, use rbfcalc3(). If
+        you  have  2-dimensional  space,  use  rbfcalc3().  If  you  have  general
+        situation (NX-dimensional space, NY-dimensional function)  you  should use
+        generic rbfcalc().
+
+        If you want to perform parallel model evaluation  from  multiple  threads,
+        use rbftscalcbuf() with per-thread buffer object.
+
+        This function returns 0.0 when:
+        * model is not initialized
+        * NX<>1
+        * NY<>1
+
+        INPUT PARAMETERS:
+            S       -   RBF model
+            X0      -   X-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 rbfcalc1(rbfmodel s,
+            double x0,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(math.isfinite(x0), "RBFCalc1: invalid value for X0 (X0 is Inf)!");
+            result = 0;
+            if( s.ny!=1 || s.nx!=1 )
+            {
+                return result;
+            }
+            if( s.modelversion==1 )
+            {
+                result = 0;
+                return result;
+            }
+            if( s.modelversion==2 )
+            {
+                result = rbfv2.rbfv2calc1(s.model2, x0, _params);
+                return result;
+            }
+            alglib.ap.assert(false, "RBFCalc1: integrity check failed");
+            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=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 generic rbfcalc().
+
+        If  you  want  to  calculate  function  values  many times, consider using 
+        rbfgridcalc2v(), which is far more efficient than many subsequent calls to
+        rbfcalc2().
+
+        If you want to perform parallel model evaluation  from  multiple  threads,
+        use rbftscalcbuf() with per-thread buffer object.
+
+        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,
+            alglib.xparams _params)
+        {
+            double result = 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 )
+            {
+                return result;
+            }
+            if( s.modelversion==1 )
+            {
+                result = rbfv1.rbfv1calc2(s.model1, x0, x1, _params);
+                return result;
+            }
+            if( s.modelversion==2 )
+            {
+                result = rbfv2.rbfv2calc2(s.model2, x0, x1, _params);
+                return result;
+            }
+            alglib.ap.assert(false, "RBFCalc2: integrity check failed");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function calculates value 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 generic rbfcalc().
+
+        If  you  want  to  calculate  function  values  many times, consider using 
+        rbfgridcalc3v(), which is far more efficient than many subsequent calls to
+        rbfcalc3().
+
+        If you want to perform parallel model evaluation  from  multiple  threads,
+        use rbftscalcbuf() with per-thread buffer object.
+
+        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,
+            alglib.xparams _params)
+        {
+            double result = 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 )
+            {
+                return result;
+            }
+            if( s.modelversion==1 )
+            {
+                result = rbfv1.rbfv1calc3(s.model1, x0, x1, x2, _params);
+                return result;
+            }
+            if( s.modelversion==2 )
+            {
+                result = rbfv2.rbfv2calc3(s.model2, x0, x1, x2, _params);
+                return result;
+            }
+            alglib.ap.assert(false, "RBFCalc3: integrity check failed");
+            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().
+
+        If you want to perform parallel model evaluation  from  multiple  threads,
+        use rbftscalcbuf() with per-thread buffer object.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "RBFCalc: X contains infinite or NaN values");
+            rbfcalcbuf(s, x, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        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.
+
+        If you want to perform parallel model evaluation  from  multiple  threads,
+        use rbftscalcbuf() with per-thread buffer object.
+
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=s.nx, "RBFCalcBuf: Length(X)<NX");
+            alglib.ap.assert(apserv.isfinitevector(x, s.nx, _params), "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.modelversion==1 )
+            {
+                rbfv1.rbfv1calcbuf(s.model1, x, ref y, _params);
+                return;
+            }
+            if( s.modelversion==2 )
+            {
+                rbfv2.rbfv2calcbuf(s.model2, x, ref y, _params);
+                return;
+            }
+            alglib.ap.assert(false, "RBFCalcBuf: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF model at the given point, using
+        external  buffer  object  (internal  temporaries  of  RBF  model  are  not
+        modified).
+
+        This function allows to use same RBF model object  in  different  threads,
+        assuming  that  different   threads  use  different  instances  of  buffer
+        structure.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, may be shared between different threads
+            Buf     -   buffer object created for this particular instance of  RBF
+                        model with rbfcreatecalcbuffer().
+            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 rbftscalcbuf(rbfmodel s,
+            rbfcalcbuffer buf,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=s.nx, "RBFCalcBuf: Length(X)<NX");
+            alglib.ap.assert(apserv.isfinitevector(x, s.nx, _params), "RBFCalcBuf: X contains infinite or NaN values");
+            alglib.ap.assert(s.modelversion==buf.modelversion, "RBFCalcBuf: buffer object is not compatible with RBF model");
+            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.modelversion==1 )
+            {
+                rbfv1.rbfv1tscalcbuf(s.model1, buf.bufv1, x, ref y, _params);
+                return;
+            }
+            if( s.modelversion==2 )
+            {
+                rbfv2.rbfv2tscalcbuf(s.model2, buf.bufv2, x, ref y, _params);
+                return;
+            }
+            alglib.ap.assert(false, "RBFTsCalcBuf: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This is legacy function for gridded calculation of RBF model.
+
+        It is superseded by rbfgridcalc2v() and  rbfgridcalc2vsubset()  functions.
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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];
+
+            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, _params), "RBFGridCalc2: X0 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x1, n1, _params), "RBFGridCalc2: X1 contains infinite or NaN values!");
+            if( s.modelversion==1 )
+            {
+                rbfv1.rbfv1gridcalc2(s.model1, x0, n0, x1, n1, ref y, _params);
+                return;
+            }
+            if( s.modelversion==2 )
+            {
+                rbfv2.rbfv2gridcalc2(s.model2, x0, n0, x1, n1, ref y, _params);
+                return;
+            }
+            alglib.ap.assert(false, "RBFGridCalc2: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF  model  at  the  regular  grid,
+        which  has  N0*N1 points, with Point[I,J] = (X0[I], X1[J]).  Vector-valued
+        RBF models are supported.
+
+        This function returns 0.0 when:
+        * model is not initialized
+        * NX<>2
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        NOTE: Parallel  processing  is  implemented only for modern (hierarchical)
+              RBFs. Legacy version 1 RBFs (created  by  QNN  or  RBF-ML) are still
+              processed serially.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, used in read-only mode, can be  shared  between
+                        multiple   invocations  of  this  function  from  multiple
+                        threads.
+            
+            X0      -   array of grid nodes, first coordinates, array[N0].
+                        Must be ordered by ascending. Exception is generated
+                        if the array is not correctly ordered.
+            N0      -   grid size (number of nodes) in the first dimension
+            
+            X1      -   array of grid nodes, second coordinates, array[N1]
+                        Must be ordered by ascending. Exception is generated
+                        if the array is not correctly ordered.
+            N1      -   grid size (number of nodes) in the second dimension
+
+        OUTPUT PARAMETERS:
+            Y       -   function values, array[NY*N0*N1], where NY is a  number of
+                        "output" vector values (this  function   supports  vector-
+                        valued RBF models). Y is out-variable and  is  reallocated
+                        by this function.
+                        Y[K+NY*(I0+I1*N0)]=F_k(X0[I0],X1[I1]), for:
+                        *  K=0...NY-1
+                        * I0=0...N0-1
+                        * I1=0...N1-1
+
+        NOTE: this function supports weakly ordered grid nodes, i.e. you may  have                
+              X[i]=X[i+1] for some i. It does  not  provide  you  any  performance
+              benefits  due  to   duplication  of  points,  just  convenience  and
+              flexibility.
+              
+        NOTE: this  function  is  re-entrant,  i.e.  you  may  use  same  rbfmodel
+              structure in multiple threads calling  this function  for  different
+              grids.
+              
+        NOTE: if you need function values on some subset  of  regular  grid, which
+              may be described as "several compact and  dense  islands",  you  may
+              use rbfgridcalc2vsubset().
+
+          -- ALGLIB --
+             Copyright 27.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfgridcalc2v(rbfmodel s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            bool[] dummy = new bool[0];
+
+            y = new double[0];
+
+            alglib.ap.assert(n0>0, "RBFGridCalc2V: invalid value for N0 (N0<=0)!");
+            alglib.ap.assert(n1>0, "RBFGridCalc2V: invalid value for N1 (N1<=0)!");
+            alglib.ap.assert(alglib.ap.len(x0)>=n0, "RBFGridCalc2V: Length(X0)<N0");
+            alglib.ap.assert(alglib.ap.len(x1)>=n1, "RBFGridCalc2V: Length(X1)<N1");
+            alglib.ap.assert(apserv.isfinitevector(x0, n0, _params), "RBFGridCalc2V: X0 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x1, n1, _params), "RBFGridCalc2V: X1 contains infinite or NaN values!");
+            for(i=0; i<=n0-2; i++)
+            {
+                alglib.ap.assert((double)(x0[i])<=(double)(x0[i+1]), "RBFGridCalc2V: X0 is not ordered by ascending");
+            }
+            for(i=0; i<=n1-2; i++)
+            {
+                alglib.ap.assert((double)(x1[i])<=(double)(x1[i+1]), "RBFGridCalc2V: X1 is not ordered by ascending");
+            }
+            rbfgridcalc2vx(s, x0, n0, x1, n1, dummy, false, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF model at some subset of regular
+        grid:
+        * grid has N0*N1 points, with Point[I,J] = (X0[I], X1[J])
+        * only values at some subset of this grid are required
+        Vector-valued RBF models are supported.
+
+        This function returns 0.0 when:
+        * model is not initialized
+        * NX<>2
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        NOTE: Parallel  processing  is  implemented only for modern (hierarchical)
+              RBFs. Legacy version 1 RBFs (created  by  QNN  or  RBF-ML) are still
+              processed serially.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, used in read-only mode, can be  shared  between
+                        multiple   invocations  of  this  function  from  multiple
+                        threads.
+            
+            X0      -   array of grid nodes, first coordinates, array[N0].
+                        Must be ordered by ascending. Exception is generated
+                        if the array is not correctly ordered.
+            N0      -   grid size (number of nodes) in the first dimension
+            
+            X1      -   array of grid nodes, second coordinates, array[N1]
+                        Must be ordered by ascending. Exception is generated
+                        if the array is not correctly ordered.
+            N1      -   grid size (number of nodes) in the second dimension
+            
+            FlagY   -   array[N0*N1]:
+                        * Y[I0+I1*N0] corresponds to node (X0[I0],X1[I1])
+                        * it is a "bitmap" array which contains  False  for  nodes
+                          which are NOT calculated, and True for nodes  which  are
+                          required.
+
+        OUTPUT PARAMETERS:
+            Y       -   function values, array[NY*N0*N1*N2], where NY is a  number
+                        of "output" vector values (this function  supports vector-
+                        valued RBF models):
+                        * Y[K+NY*(I0+I1*N0)]=F_k(X0[I0],X1[I1]),
+                          for K=0...NY-1, I0=0...N0-1, I1=0...N1-1.
+                        * elements of Y[] which correspond  to  FlagY[]=True   are
+                          loaded by model values (which may be  exactly  zero  for
+                          some nodes).
+                        * elements of Y[] which correspond to FlagY[]=False MAY be
+                          initialized by zeros OR may be calculated. This function
+                          processes  grid  as  a  hierarchy  of  nested blocks and
+                          micro-rows. If just one element of micro-row is required,
+                          entire micro-row (up to 8 nodes in the current  version,
+                          but no promises) is calculated.
+
+        NOTE: this function supports weakly ordered grid nodes, i.e. you may  have                
+              X[i]=X[i+1] for some i. It does  not  provide  you  any  performance
+              benefits  due  to   duplication  of  points,  just  convenience  and
+              flexibility.
+              
+        NOTE: this  function  is  re-entrant,  i.e.  you  may  use  same  rbfmodel
+              structure in multiple threads calling  this function  for  different
+              grids.
+
+          -- ALGLIB --
+             Copyright 04.03.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfgridcalc2vsubset(rbfmodel s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            bool[] flagy,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            y = new double[0];
+
+            alglib.ap.assert(n0>0, "RBFGridCalc2VSubset: invalid value for N0 (N0<=0)!");
+            alglib.ap.assert(n1>0, "RBFGridCalc2VSubset: invalid value for N1 (N1<=0)!");
+            alglib.ap.assert(alglib.ap.len(x0)>=n0, "RBFGridCalc2VSubset: Length(X0)<N0");
+            alglib.ap.assert(alglib.ap.len(x1)>=n1, "RBFGridCalc2VSubset: Length(X1)<N1");
+            alglib.ap.assert(alglib.ap.len(flagy)>=n0*n1, "RBFGridCalc2VSubset: Length(FlagY)<N0*N1*N2");
+            alglib.ap.assert(apserv.isfinitevector(x0, n0, _params), "RBFGridCalc2VSubset: X0 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x1, n1, _params), "RBFGridCalc2VSubset: X1 contains infinite or NaN values!");
+            for(i=0; i<=n0-2; i++)
+            {
+                alglib.ap.assert((double)(x0[i])<=(double)(x0[i+1]), "RBFGridCalc2VSubset: X0 is not ordered by ascending");
+            }
+            for(i=0; i<=n1-2; i++)
+            {
+                alglib.ap.assert((double)(x1[i])<=(double)(x1[i+1]), "RBFGridCalc2VSubset: X1 is not ordered by ascending");
+            }
+            rbfgridcalc2vx(s, x0, n0, x1, n1, flagy, true, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF  model  at  the  regular  grid,
+        which  has  N0*N1*N2  points,  with  Point[I,J,K] = (X0[I], X1[J], X2[K]).
+        Vector-valued RBF models are supported.
+
+        This function returns 0.0 when:
+        * model is not initialized
+        * NX<>3
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        NOTE: Parallel  processing  is  implemented only for modern (hierarchical)
+              RBFs. Legacy version 1 RBFs (created  by  QNN  or  RBF-ML) are still
+              processed serially.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, used in read-only mode, can be  shared  between
+                        multiple   invocations  of  this  function  from  multiple
+                        threads.
+            
+            X0      -   array of grid nodes, first coordinates, array[N0].
+                        Must be ordered by ascending. Exception is generated
+                        if the array is not correctly ordered.
+            N0      -   grid size (number of nodes) in the first dimension
+            
+            X1      -   array of grid nodes, second coordinates, array[N1]
+                        Must be ordered by ascending. Exception is generated
+                        if the array is not correctly ordered.
+            N1      -   grid size (number of nodes) in the second dimension
+            
+            X2      -   array of grid nodes, third coordinates, array[N2]
+                        Must be ordered by ascending. Exception is generated
+                        if the array is not correctly ordered.
+            N2      -   grid size (number of nodes) in the third dimension
+
+        OUTPUT PARAMETERS:
+            Y       -   function values, array[NY*N0*N1*N2], where NY is a  number
+                        of "output" vector values (this function  supports vector-
+                        valued RBF models). Y is out-variable and  is  reallocated
+                        by this function.
+                        Y[K+NY*(I0+I1*N0+I2*N0*N1)]=F_k(X0[I0],X1[I1],X2[I2]), for:
+                        *  K=0...NY-1
+                        * I0=0...N0-1
+                        * I1=0...N1-1
+                        * I2=0...N2-1
+
+        NOTE: this function supports weakly ordered grid nodes, i.e. you may  have                
+              X[i]=X[i+1] for some i. It does  not  provide  you  any  performance
+              benefits  due  to   duplication  of  points,  just  convenience  and
+              flexibility.
+              
+        NOTE: this  function  is  re-entrant,  i.e.  you  may  use  same  rbfmodel
+              structure in multiple threads calling  this function  for  different
+              grids.
+              
+        NOTE: if you need function values on some subset  of  regular  grid, which
+              may be described as "several compact and  dense  islands",  you  may
+              use rbfgridcalc3vsubset().
+
+          -- ALGLIB --
+             Copyright 04.03.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfgridcalc3v(rbfmodel s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            double[] x2,
+            int n2,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            bool[] dummy = new bool[0];
+
+            y = new double[0];
+
+            alglib.ap.assert(n0>0, "RBFGridCalc3V: invalid value for N0 (N0<=0)!");
+            alglib.ap.assert(n1>0, "RBFGridCalc3V: invalid value for N1 (N1<=0)!");
+            alglib.ap.assert(n2>0, "RBFGridCalc3V: invalid value for N2 (N2<=0)!");
+            alglib.ap.assert(alglib.ap.len(x0)>=n0, "RBFGridCalc3V: Length(X0)<N0");
+            alglib.ap.assert(alglib.ap.len(x1)>=n1, "RBFGridCalc3V: Length(X1)<N1");
+            alglib.ap.assert(alglib.ap.len(x2)>=n2, "RBFGridCalc3V: Length(X2)<N2");
+            alglib.ap.assert(apserv.isfinitevector(x0, n0, _params), "RBFGridCalc3V: X0 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x1, n1, _params), "RBFGridCalc3V: X1 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x2, n2, _params), "RBFGridCalc3V: X2 contains infinite or NaN values!");
+            for(i=0; i<=n0-2; i++)
+            {
+                alglib.ap.assert((double)(x0[i])<=(double)(x0[i+1]), "RBFGridCalc3V: X0 is not ordered by ascending");
+            }
+            for(i=0; i<=n1-2; i++)
+            {
+                alglib.ap.assert((double)(x1[i])<=(double)(x1[i+1]), "RBFGridCalc3V: X1 is not ordered by ascending");
+            }
+            for(i=0; i<=n2-2; i++)
+            {
+                alglib.ap.assert((double)(x2[i])<=(double)(x2[i+1]), "RBFGridCalc3V: X2 is not ordered by ascending");
+            }
+            rbfgridcalc3vx(s, x0, n0, x1, n1, x2, n2, dummy, false, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        This function calculates values of the RBF model at some subset of regular
+        grid:
+        * grid has N0*N1*N2 points, with Point[I,J,K] = (X0[I], X1[J], X2[K])
+        * only values at some subset of this grid are required
+        Vector-valued RBF models are supported.
+
+        This function returns 0.0 when:
+        * model is not initialized
+        * NX<>3
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        NOTE: Parallel  processing  is  implemented only for modern (hierarchical)
+              RBFs. Legacy version 1 RBFs (created  by  QNN  or  RBF-ML) are still
+              processed serially.
+
+        INPUT PARAMETERS:
+            S       -   RBF model, used in read-only mode, can be  shared  between
+                        multiple   invocations  of  this  function  from  multiple
+                        threads.
+            
+            X0      -   array of grid nodes, first coordinates, array[N0].
+                        Must be ordered by ascending. Exception is generated
+                        if the array is not correctly ordered.
+            N0      -   grid size (number of nodes) in the first dimension
+            
+            X1      -   array of grid nodes, second coordinates, array[N1]
+                        Must be ordered by ascending. Exception is generated
+                        if the array is not correctly ordered.
+            N1      -   grid size (number of nodes) in the second dimension
+            
+            X2      -   array of grid nodes, third coordinates, array[N2]
+                        Must be ordered by ascending. Exception is generated
+                        if the array is not correctly ordered.
+            N2      -   grid size (number of nodes) in the third dimension
+            
+            FlagY   -   array[N0*N1*N2]:
+                        * Y[I0+I1*N0+I2*N0*N1] corresponds to node (X0[I0],X1[I1],X2[I2])
+                        * it is a "bitmap" array which contains  False  for  nodes
+                          which are NOT calculated, and True for nodes  which  are
+                          required.
+
+        OUTPUT PARAMETERS:
+            Y       -   function values, array[NY*N0*N1*N2], where NY is a  number
+                        of "output" vector values (this function  supports vector-
+                        valued RBF models):
+                        * Y[K+NY*(I0+I1*N0+I2*N0*N1)]=F_k(X0[I0],X1[I1],X2[I2]),
+                          for K=0...NY-1, I0=0...N0-1, I1=0...N1-1, I2=0...N2-1.
+                        * elements of Y[] which correspond  to  FlagY[]=True   are
+                          loaded by model values (which may be  exactly  zero  for
+                          some nodes).
+                        * elements of Y[] which correspond to FlagY[]=False MAY be
+                          initialized by zeros OR may be calculated. This function
+                          processes  grid  as  a  hierarchy  of  nested blocks and
+                          micro-rows. If just one element of micro-row is required,
+                          entire micro-row (up to 8 nodes in the current  version,
+                          but no promises) is calculated.
+
+        NOTE: this function supports weakly ordered grid nodes, i.e. you may  have                
+              X[i]=X[i+1] for some i. It does  not  provide  you  any  performance
+              benefits  due  to   duplication  of  points,  just  convenience  and
+              flexibility.
+              
+        NOTE: this  function  is  re-entrant,  i.e.  you  may  use  same  rbfmodel
+              structure in multiple threads calling  this function  for  different
+              grids.
+
+          -- ALGLIB --
+             Copyright 04.03.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfgridcalc3vsubset(rbfmodel s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            double[] x2,
+            int n2,
+            bool[] flagy,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            y = new double[0];
+
+            alglib.ap.assert(n0>0, "RBFGridCalc3VSubset: invalid value for N0 (N0<=0)!");
+            alglib.ap.assert(n1>0, "RBFGridCalc3VSubset: invalid value for N1 (N1<=0)!");
+            alglib.ap.assert(n2>0, "RBFGridCalc3VSubset: invalid value for N2 (N2<=0)!");
+            alglib.ap.assert(alglib.ap.len(x0)>=n0, "RBFGridCalc3VSubset: Length(X0)<N0");
+            alglib.ap.assert(alglib.ap.len(x1)>=n1, "RBFGridCalc3VSubset: Length(X1)<N1");
+            alglib.ap.assert(alglib.ap.len(x2)>=n2, "RBFGridCalc3VSubset: Length(X2)<N2");
+            alglib.ap.assert(alglib.ap.len(flagy)>=n0*n1*n2, "RBFGridCalc3VSubset: Length(FlagY)<N0*N1*N2");
+            alglib.ap.assert(apserv.isfinitevector(x0, n0, _params), "RBFGridCalc3VSubset: X0 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x1, n1, _params), "RBFGridCalc3VSubset: X1 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x2, n2, _params), "RBFGridCalc3VSubset: X2 contains infinite or NaN values!");
+            for(i=0; i<=n0-2; i++)
+            {
+                alglib.ap.assert((double)(x0[i])<=(double)(x0[i+1]), "RBFGridCalc3VSubset: X0 is not ordered by ascending");
+            }
+            for(i=0; i<=n1-2; i++)
+            {
+                alglib.ap.assert((double)(x1[i])<=(double)(x1[i+1]), "RBFGridCalc3VSubset: X1 is not ordered by ascending");
+            }
+            for(i=0; i<=n2-2; i++)
+            {
+                alglib.ap.assert((double)(x2[i])<=(double)(x2[i+1]), "RBFGridCalc3VSubset: X2 is not ordered by ascending");
+            }
+            rbfgridcalc3vx(s, x0, n0, x1, n1, x2, n2, flagy, true, ref y, _params);
+        }
+
+
+        /*************************************************************************
+        This function, depending on SparseY, acts as RBFGridCalc2V (SparseY=False)
+        or RBFGridCalc2VSubset (SparseY=True) function.  See  comments  for  these
+        functions for more information
+
+          -- ALGLIB --
+             Copyright 04.03.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfgridcalc2vx(rbfmodel s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            bool[] flagy,
+            bool sparsey,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int nx = 0;
+            int ny = 0;
+            int ylen = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+            double[] dummyx2 = new double[0];
+            double[] dummyx3 = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int l = 0;
+            double[] tx = new double[0];
+            double[] ty = new double[0];
+            int dstoffs = 0;
+            rbfcalcbuffer calcbuf = new rbfcalcbuffer();
+
+            alglib.ap.assert(n0>0, "RBFGridCalc2VX: invalid value for N0 (N0<=0)!");
+            alglib.ap.assert(n1>0, "RBFGridCalc2VX: invalid value for N1 (N1<=0)!");
+            alglib.ap.assert(alglib.ap.len(x0)>=n0, "RBFGridCalc2VX: Length(X0)<N0");
+            alglib.ap.assert(alglib.ap.len(x1)>=n1, "RBFGridCalc2VX: Length(X1)<N1");
+            alglib.ap.assert(apserv.isfinitevector(x0, n0, _params), "RBFGridCalc2VX: X0 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x1, n1, _params), "RBFGridCalc2VX: X1 contains infinite or NaN values!");
+            for(i=0; i<=n0-2; i++)
+            {
+                alglib.ap.assert((double)(x0[i])<=(double)(x0[i+1]), "RBFGridCalc2VX: X0 is not ordered by ascending");
+            }
+            for(i=0; i<=n1-2; i++)
+            {
+                alglib.ap.assert((double)(x1[i])<=(double)(x1[i+1]), "RBFGridCalc2VX: X1 is not ordered by ascending");
+            }
+            
+            //
+            // Prepare local variables
+            //
+            nx = s.nx;
+            ny = s.ny;
+            hqrnd.hqrndseed(325, 46345, rs, _params);
+            
+            //
+            // Prepare output array
+            //
+            ylen = ny*n0*n1;
+            y = new double[ylen];
+            for(i=0; i<=ylen-1; i++)
+            {
+                y[i] = 0;
+            }
+            if( s.nx!=2 )
+            {
+                return;
+            }
+            
+            //
+            // Process V2 model
+            //
+            if( s.modelversion==2 )
+            {
+                dummyx2 = new double[1];
+                dummyx2[0] = 0;
+                dummyx3 = new double[1];
+                dummyx3[0] = 0;
+                rbfv2.rbfv2gridcalcvx(s.model2, x0, n0, x1, n1, dummyx2, 1, dummyx3, 1, flagy, sparsey, y, _params);
+                return;
+            }
+            
+            //
+            // Reference code for V1 models
+            //
+            if( s.modelversion==1 )
+            {
+                tx = new double[nx];
+                rbfcreatecalcbuffer(s, calcbuf, _params);
+                for(i=0; i<=n0-1; i++)
+                {
+                    for(j=0; j<=n1-1; j++)
+                    {
+                        k = i+j*n0;
+                        dstoffs = ny*k;
+                        if( sparsey && !flagy[k] )
+                        {
+                            for(l=0; l<=ny-1; l++)
+                            {
+                                y[l+dstoffs] = 0;
+                            }
+                            continue;
+                        }
+                        tx[0] = x0[i];
+                        tx[1] = x1[j];
+                        rbftscalcbuf(s, calcbuf, tx, ref ty, _params);
+                        for(l=0; l<=ny-1; l++)
+                        {
+                            y[l+dstoffs] = ty[l];
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Unknown model
+            //
+            alglib.ap.assert(false, "RBFGradCalc3VX: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This function, depending on SparseY, acts as RBFGridCalc3V (SparseY=False)
+        or RBFGridCalc3VSubset (SparseY=True) function.  See  comments  for  these
+        functions for more information
+
+          -- ALGLIB --
+             Copyright 04.03.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfgridcalc3vx(rbfmodel s,
+            double[] x0,
+            int n0,
+            double[] x1,
+            int n1,
+            double[] x2,
+            int n2,
+            bool[] flagy,
+            bool sparsey,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int ylen = 0;
+            int nx = 0;
+            int ny = 0;
+            double rmax = 0;
+            int[] blocks0 = new int[0];
+            int[] blocks1 = new int[0];
+            int[] blocks2 = new int[0];
+            int blockscnt0 = 0;
+            int blockscnt1 = 0;
+            int blockscnt2 = 0;
+            double blockwidth = 0;
+            double searchradius = 0;
+            double avgfuncpernode = 0;
+            int ntrials = 0;
+            int maxblocksize = 0;
+            rbfv1.gridcalc3v1buf bufseedv1 = new rbfv1.gridcalc3v1buf();
+            alglib.smp.shared_pool bufpool = new alglib.smp.shared_pool();
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+            double[] dummyx3 = new double[0];
+
+            alglib.ap.assert(n0>0, "RBFGridCalc3V: invalid value for N0 (N0<=0)!");
+            alglib.ap.assert(n1>0, "RBFGridCalc3V: invalid value for N1 (N1<=0)!");
+            alglib.ap.assert(n2>0, "RBFGridCalc3V: invalid value for N2 (N2<=0)!");
+            alglib.ap.assert(alglib.ap.len(x0)>=n0, "RBFGridCalc3V: Length(X0)<N0");
+            alglib.ap.assert(alglib.ap.len(x1)>=n1, "RBFGridCalc3V: Length(X1)<N1");
+            alglib.ap.assert(alglib.ap.len(x2)>=n2, "RBFGridCalc3V: Length(X2)<N2");
+            alglib.ap.assert(apserv.isfinitevector(x0, n0, _params), "RBFGridCalc3V: X0 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x1, n1, _params), "RBFGridCalc3V: X1 contains infinite or NaN values!");
+            alglib.ap.assert(apserv.isfinitevector(x2, n2, _params), "RBFGridCalc3V: X2 contains infinite or NaN values!");
+            for(i=0; i<=n0-2; i++)
+            {
+                alglib.ap.assert((double)(x0[i])<=(double)(x0[i+1]), "RBFGridCalc3V: X0 is not ordered by ascending");
+            }
+            for(i=0; i<=n1-2; i++)
+            {
+                alglib.ap.assert((double)(x1[i])<=(double)(x1[i+1]), "RBFGridCalc3V: X1 is not ordered by ascending");
+            }
+            for(i=0; i<=n2-2; i++)
+            {
+                alglib.ap.assert((double)(x2[i])<=(double)(x2[i+1]), "RBFGridCalc3V: X2 is not ordered by ascending");
+            }
+            
+            //
+            // Prepare local variables
+            //
+            nx = s.nx;
+            ny = s.ny;
+            hqrnd.hqrndseed(325, 46345, rs, _params);
+            
+            //
+            // Prepare output array
+            //
+            ylen = ny*n0*n1*n2;
+            y = new double[ylen];
+            for(i=0; i<=ylen-1; i++)
+            {
+                y[i] = 0;
+            }
+            if( s.nx!=3 )
+            {
+                return;
+            }
+            
+            //
+            // Process V1 model
+            //
+            if( s.modelversion==1 )
+            {
+                
+                //
+                // Fast exit for models without centers
+                //
+                if( s.model1.nc==0 )
+                {
+                    return;
+                }
+                
+                //
+                // Prepare seed, create shared pool of temporary buffers
+                //
+                bufseedv1.cx = new double[nx];
+                bufseedv1.tx = new double[nx];
+                bufseedv1.ty = new double[ny];
+                bufseedv1.expbuf0 = new double[n0];
+                bufseedv1.expbuf1 = new double[n1];
+                bufseedv1.expbuf2 = new double[n2];
+                nearestneighbor.kdtreecreaterequestbuffer(s.model1.tree, bufseedv1.requestbuf, _params);
+                alglib.smp.ae_shared_pool_set_seed(bufpool, bufseedv1);
+                
+                //
+                // Analyze input grid:
+                // * analyze average number of basis functions per grid node
+                // * partition grid in into blocks
+                //
+                rmax = s.model1.rmax;
+                blockwidth = 2*rmax;
+                maxblocksize = 8;
+                searchradius = rmax*rbffarradius+0.5*Math.Sqrt(s.nx)*blockwidth;
+                ntrials = 100;
+                avgfuncpernode = 0.0;
+                for(i=0; i<=ntrials-1; i++)
+                {
+                    bufseedv1.tx[0] = x0[hqrnd.hqrnduniformi(rs, n0, _params)];
+                    bufseedv1.tx[1] = x1[hqrnd.hqrnduniformi(rs, n1, _params)];
+                    bufseedv1.tx[2] = x2[hqrnd.hqrnduniformi(rs, n2, _params)];
+                    avgfuncpernode = avgfuncpernode+(double)nearestneighbor.kdtreetsqueryrnn(s.model1.tree, bufseedv1.requestbuf, bufseedv1.tx, searchradius, true, _params)/(double)ntrials;
+                }
+                blocks0 = new int[n0+1];
+                blockscnt0 = 0;
+                blocks0[0] = 0;
+                for(i=1; i<=n0-1; i++)
+                {
+                    if( (double)(x0[i]-x0[blocks0[blockscnt0]])>(double)(blockwidth) || i-blocks0[blockscnt0]>=maxblocksize )
+                    {
+                        apserv.inc(ref blockscnt0, _params);
+                        blocks0[blockscnt0] = i;
+                    }
+                }
+                apserv.inc(ref blockscnt0, _params);
+                blocks0[blockscnt0] = n0;
+                blocks1 = new int[n1+1];
+                blockscnt1 = 0;
+                blocks1[0] = 0;
+                for(i=1; i<=n1-1; i++)
+                {
+                    if( (double)(x1[i]-x1[blocks1[blockscnt1]])>(double)(blockwidth) || i-blocks1[blockscnt1]>=maxblocksize )
+                    {
+                        apserv.inc(ref blockscnt1, _params);
+                        blocks1[blockscnt1] = i;
+                    }
+                }
+                apserv.inc(ref blockscnt1, _params);
+                blocks1[blockscnt1] = n1;
+                blocks2 = new int[n2+1];
+                blockscnt2 = 0;
+                blocks2[0] = 0;
+                for(i=1; i<=n2-1; i++)
+                {
+                    if( (double)(x2[i]-x2[blocks2[blockscnt2]])>(double)(blockwidth) || i-blocks2[blockscnt2]>=maxblocksize )
+                    {
+                        apserv.inc(ref blockscnt2, _params);
+                        blocks2[blockscnt2] = i;
+                    }
+                }
+                apserv.inc(ref blockscnt2, _params);
+                blocks2[blockscnt2] = n2;
+                
+                //
+                // Perform calculation in multithreaded mode
+                //
+                rbfv1.rbfv1gridcalc3vrec(s.model1, x0, n0, x1, n1, x2, n2, blocks0, 0, blockscnt0, blocks1, 0, blockscnt1, blocks2, 0, blockscnt2, flagy, sparsey, searchradius, avgfuncpernode, bufpool, y, _params);
+                
+                //
+                // Done
+                //
+                return;
+            }
+            
+            //
+            // Process V2 model
+            //
+            if( s.modelversion==2 )
+            {
+                dummyx3 = new double[1];
+                dummyx3[0] = 0;
+                rbfv2.rbfv2gridcalcvx(s.model2, x0, n0, x1, n1, x2, n2, dummyx3, 1, flagy, sparsey, y, _params);
+                return;
+            }
+            
+            //
+            // Unknown model
+            //
+            alglib.ap.assert(false, "RBFGradCalc3VX: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        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
+                        For ModelVersion=1:
+                        * last column       - radius, same for all dimensions of
+                                              the function being modelled
+                        For ModelVersion=2:
+                        * last NX columns   - radii, one per dimension
+            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.
+            ModelVersion-version of the RBF model:
+                        * 1 - for models created by QNN and RBF-ML algorithms,
+                          compatible with ALGLIB 3.10 or earlier.
+                        * 2 - for models created by HierarchicalRBF, requires
+                          ALGLIB 3.11 or later
+
+          -- 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,
+            ref int modelversion,
+            alglib.xparams _params)
+        {
+            nx = 0;
+            ny = 0;
+            xwr = new double[0,0];
+            nc = 0;
+            v = new double[0,0];
+            modelversion = 0;
+
+            if( s.modelversion==1 )
+            {
+                modelversion = 1;
+                rbfv1.rbfv1unpack(s.model1, ref nx, ref ny, ref xwr, ref nc, ref v, _params);
+                return;
+            }
+            if( s.modelversion==2 )
+            {
+                modelversion = 2;
+                rbfv2.rbfv2unpack(s.model2, ref nx, ref ny, ref xwr, ref nc, ref v, _params);
+                return;
+            }
+            alglib.ap.assert(false, "RBFUnpack: integrity check failure");
+        }
+
+
+        /*************************************************************************
+        This function returns model version.
+
+        INPUT PARAMETERS:
+            S       -   RBF model
+
+        RESULT:
+            * 1 - for models created by QNN and RBF-ML algorithms,
+              compatible with ALGLIB 3.10 or earlier.
+            * 2 - for models created by HierarchicalRBF, requires
+              ALGLIB 3.11 or later
+
+          -- ALGLIB --
+             Copyright 06.07.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static int rbfgetmodelversion(rbfmodel s,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = s.modelversion;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function is used to peek into hierarchical RBF  construction  process
+        from  some  other  thread  and  get current progress indicator. It returns
+        value in [0,1].
+
+        IMPORTANT: only HRBFs (hierarchical RBFs) support  peeking  into  progress
+                   indicator. Legacy RBF-ML and RBF-QNN do  not  support  it.  You
+                   will always get 0 value.
+
+        INPUT PARAMETERS:
+            S           -   RBF model object
+
+        RESULT:
+            progress value, in [0,1]
+
+          -- ALGLIB --
+             Copyright 17.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static double rbfpeekprogress(rbfmodel s,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = (double)s.progress10000/(double)10000;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function  is  used  to  submit  a  request  for  termination  of  the
+        hierarchical RBF construction process from some other thread.  As  result,
+        RBF construction is terminated smoothly (with proper deallocation  of  all
+        necessary resources) and resultant model is filled by zeros.
+
+        A rep.terminationtype=8 will be returned upon receiving such request.
+
+        IMPORTANT: only  HRBFs  (hierarchical  RBFs) support termination requests.
+                   Legacy RBF-ML and RBF-QNN do not  support  it.  An  attempt  to
+                   terminate their construction will be ignored.
+
+        IMPORTANT: termination request flag is cleared when the model construction
+                   starts. Thus, any pre-construction termination requests will be
+                   silently ignored - only ones submitted AFTER  construction  has
+                   actually began will be handled.
+
+        INPUT PARAMETERS:
+            S           -   RBF model object
+
+          -- ALGLIB --
+             Copyright 17.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfrequesttermination(rbfmodel s,
+            alglib.xparams _params)
+        {
+            s.terminationrequest = true;
+        }
+
+
+        /*************************************************************************
+        Serializer: allocation
+
+          -- ALGLIB --
+             Copyright 02.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfalloc(alglib.serializer s,
+            rbfmodel model,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Header
+            //
+            s.alloc_entry();
+            
+            //
+            // V1 model
+            //
+            if( model.modelversion==1 )
+            {
+                
+                //
+                // Header
+                //
+                s.alloc_entry();
+                rbfv1.rbfv1alloc(s, model.model1, _params);
+                return;
+            }
+            
+            //
+            // V2 model
+            //
+            if( model.modelversion==2 )
+            {
+                
+                //
+                // Header
+                //
+                s.alloc_entry();
+                rbfv2.rbfv2alloc(s, model.model2, _params);
+                return;
+            }
+            alglib.ap.assert(false);
+        }
+
+
+        /*************************************************************************
+        Serializer: serialization
+
+          -- ALGLIB --
+             Copyright 02.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfserialize(alglib.serializer s,
+            rbfmodel model,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Header
+            //
+            s.serialize_int(scodes.getrbfserializationcode(_params));
+            
+            //
+            // V1 model
+            //
+            if( model.modelversion==1 )
+            {
+                s.serialize_int(rbffirstversion);
+                rbfv1.rbfv1serialize(s, model.model1, _params);
+                return;
+            }
+            
+            //
+            // V2 model
+            //
+            if( model.modelversion==2 )
+            {
+                
+                //
+                // Header
+                //
+                s.serialize_int(rbfversion2);
+                rbfv2.rbfv2serialize(s, model.model2, _params);
+                return;
+            }
+            alglib.ap.assert(false);
+        }
+
+
+        /*************************************************************************
+        Serializer: unserialization
+
+          -- ALGLIB --
+             Copyright 02.02.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rbfunserialize(alglib.serializer s,
+            rbfmodel model,
+            alglib.xparams _params)
+        {
+            int i0 = 0;
+            int i1 = 0;
+
+            rbfpreparenonserializablefields(model, _params);
+            
+            //
+            // Header
+            //
+            i0 = s.unserialize_int();
+            alglib.ap.assert(i0==scodes.getrbfserializationcode(_params), "RBFUnserialize: stream header corrupted");
+            i1 = s.unserialize_int();
+            alglib.ap.assert(i1==rbffirstversion || i1==rbfversion2, "RBFUnserialize: stream header corrupted");
+            
+            //
+            // V1 model
+            //
+            if( i1==rbffirstversion )
+            {
+                rbfv1.rbfv1unserialize(s, model.model1, _params);
+                model.modelversion = 1;
+                model.ny = model.model1.ny;
+                model.nx = model.model1.nx;
+                initializev2(model.nx, model.ny, model.model2, _params);
+                return;
+            }
+            
+            //
+            // V2 model
+            //
+            if( i1==rbfversion2 )
+            {
+                rbfv2.rbfv2unserialize(s, model.model2, _params);
+                model.modelversion = 2;
+                model.ny = model.model2.ny;
+                model.nx = model.model2.nx;
+                initializev1(model.nx, model.ny, model.model1, _params);
+                return;
+            }
+            alglib.ap.assert(false);
+        }
+
+
+        /*************************************************************************
+        Initialize empty model
+
+          -- ALGLIB --
+             Copyright 12.05.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rbfpreparenonserializablefields(rbfmodel s,
+            alglib.xparams _params)
+        {
+            s.n = 0;
+            s.hasscale = false;
+            s.radvalue = 1;
+            s.radzvalue = 5;
+            s.nlayers = 0;
+            s.lambdav = 0;
+            s.aterm = 1;
+            s.algorithmtype = 0;
+            s.epsort = eps;
+            s.epserr = eps;
+            s.maxits = 0;
+            s.nnmaxits = 100;
+        }
+
+
+        /*************************************************************************
+        Initialize V1 model (skip initialization for NX=1 or NX>3)
+
+          -- ALGLIB --
+             Copyright 12.05.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static void initializev1(int nx,
+            int ny,
+            rbfv1.rbfv1model s,
+            alglib.xparams _params)
+        {
+            if( nx==2 || nx==3 )
+            {
+                rbfv1.rbfv1create(nx, ny, s, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Initialize V2 model
+
+          -- ALGLIB --
+             Copyright 12.05.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static void initializev2(int nx,
+            int ny,
+            rbfv2.rbfv2model s,
+            alglib.xparams _params)
+        {
+            rbfv2.rbfv2create(nx, ny, s, _params);
+        }
+
+
+        /*************************************************************************
+        Cleans report fields
+
+          -- ALGLIB --
+             Copyright 16.06.2016 by Bochkanov Sergey
+        *************************************************************************/
+        private static void clearreportfields(rbfreport rep,
+            alglib.xparams _params)
+        {
+            rep.rmserror = Double.NaN;
+            rep.maxerror = Double.NaN;
+            rep.arows = 0;
+            rep.acols = 0;
+            rep.annz = 0;
+            rep.iterationscount = 0;
+            rep.nmv = 0;
+            rep.terminationtype = 0;
+        }
+
+
+    }
+    public class intcomp
+    {
+        /*************************************************************************
+        This function is left for backward compatibility.
+        Use fitspheremc() instead.
+
+                                            
+          -- ALGLIB --
+             Copyright 14.04.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void nsfitspheremcc(double[,] xy,
+            int npoints,
+            int nx,
+            ref double[] cx,
+            ref double rhi,
+            alglib.xparams _params)
+        {
+            double dummy = 0;
+
+            cx = new double[0];
+            rhi = 0;
+
+            nsfitspherex(xy, npoints, nx, 1, 0.0, 0, 0.0, ref cx, ref dummy, ref rhi, _params);
+        }
+
+
+        /*************************************************************************
+        This function is left for backward compatibility.
+        Use fitspheremi() instead.
+                                            
+          -- ALGLIB --
+             Copyright 14.04.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void nsfitspheremic(double[,] xy,
+            int npoints,
+            int nx,
+            ref double[] cx,
+            ref double rlo,
+            alglib.xparams _params)
+        {
+            double dummy = 0;
+
+            cx = new double[0];
+            rlo = 0;
+
+            nsfitspherex(xy, npoints, nx, 2, 0.0, 0, 0.0, ref cx, ref rlo, ref dummy, _params);
+        }
+
+
+        /*************************************************************************
+        This function is left for backward compatibility.
+        Use fitspheremz() instead.
+                                            
+          -- ALGLIB --
+             Copyright 14.04.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void nsfitspheremzc(double[,] xy,
+            int npoints,
+            int nx,
+            ref double[] cx,
+            ref double rlo,
+            ref double rhi,
+            alglib.xparams _params)
+        {
+            cx = new double[0];
+            rlo = 0;
+            rhi = 0;
+
+            nsfitspherex(xy, npoints, nx, 3, 0.0, 0, 0.0, ref cx, ref rlo, ref rhi, _params);
+        }
+
+
+        /*************************************************************************
+        This function is left for backward compatibility.
+        Use fitspherex() instead.
+                                            
+          -- ALGLIB --
+             Copyright 14.04.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void nsfitspherex(double[,] xy,
+            int npoints,
+            int nx,
+            int problemtype,
+            double epsx,
+            int aulits,
+            double penalty,
+            ref double[] cx,
+            ref double rlo,
+            ref double rhi,
+            alglib.xparams _params)
+        {
+            cx = new double[0];
+            rlo = 0;
+            rhi = 0;
+
+            fitsphere.fitspherex(xy, npoints, nx, problemtype, epsx, aulits, penalty, ref cx, ref rlo, ref rhi, _params);
+        }
+
+
+    }
+}
+
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/linalg.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/linalg.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/linalg.cs	(revision 17158)
@@ -0,0 +1,47983 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 164
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    Sparse matrix structure.
+
+    You should use ALGLIB functions to work with sparse matrix. Never  try  to
+    access its fields directly!
+
+    NOTES ON THE SPARSE STORAGE FORMATS
+
+    Sparse matrices can be stored using several formats:
+    * Hash-Table representation
+    * Compressed Row Storage (CRS)
+    * Skyline matrix storage (SKS)
+
+    Each of the formats has benefits and drawbacks:
+    * Hash-table is good for dynamic operations (insertion of new elements),
+      but does not support linear algebra operations
+    * CRS is good for operations like matrix-vector or matrix-matrix products,
+      but its initialization is less convenient - you have to tell row   sizes
+      at the initialization, and you have to fill  matrix  only  row  by  row,
+      from left to right.
+    * SKS is a special format which is used to store triangular  factors  from
+      Cholesky factorization. It does not support  dynamic  modification,  and
+      support for linear algebra operations is very limited.
+
+    Tables below outline information about these two formats:
+
+        OPERATIONS WITH MATRIX      HASH        CRS         SKS
+        creation                    +           +           +
+        SparseGet                   +           +           +
+        SparseRewriteExisting       +           +           +
+        SparseSet                   +           +           +
+        SparseAdd                   +
+        SparseGetRow                            +           +
+        SparseGetCompressedRow                  +           +
+        sparse-dense linear algebra             +           +
+    *************************************************************************/
+    public class sparsematrix : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public sparsematrix()
+        {
+            _innerobj = new sparse.sparsematrix();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new sparsematrix((sparse.sparsematrix)_innerobj.make_copy());
+        }
+    
+        //
+        // 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;
+        }
+    }
+
+
+    /*************************************************************************
+    Temporary buffers for sparse matrix operations.
+
+    You should pass an instance of this structure to factorization  functions.
+    It allows to reuse memory during repeated sparse  factorizations.  You  do
+    not have to call some initialization function - simply passing an instance
+    to factorization function is enough.
+    *************************************************************************/
+    public class sparsebuffers : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public sparsebuffers()
+        {
+            _innerobj = new sparse.sparsebuffers();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new sparsebuffers((sparse.sparsebuffers)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private sparse.sparsebuffers _innerobj;
+        public sparse.sparsebuffers innerobj { get { return _innerobj; } }
+        public sparsebuffers(sparse.sparsebuffers 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
+
+    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.
+
+    NOTE 2
+
+    Comments of SparseMatrix structure outline  information  about  different
+    sparse storage formats. We recommend you to read them before starting  to
+    use ALGLIB sparse matrices.
+
+    NOTE 3
+
+    This function completely  overwrites S with new sparse matrix. Previously
+    allocated storage is NOT reused. If you  want  to reuse already allocated
+    memory, call SparseCreateBuf function.
+
+      -- 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, null);
+    }
+    
+    public static void sparsecreate(int m, int n, int k, out sparsematrix s, alglib.xparams _params)
+    {
+        s = new sparsematrix();
+        sparse.sparsecreate(m, n, k, s.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void sparsecreate(int m, int n, out sparsematrix s, alglib.xparams _params)
+    {
+        int k;
+    
+        s = new sparsematrix();
+        k = 0;
+        sparse.sparsecreate(m, n, k, s.innerobj, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This version of SparseCreate function creates sparse matrix in Hash-Table
+    format, reusing previously allocated storage as much  as  possible.  Read
+    comments for SparseCreate() for more information.
+
+    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.
+        S           -   SparseMatrix structure which MAY contain some  already
+                        allocated storage.
+
+    OUTPUT PARAMETERS
+        S           -   sparse M*N matrix in Hash-Table representation.
+                        All elements of the matrix are zero.
+                        Previously allocated storage is reused, if  its  size
+                        is compatible with expected number of non-zeros K.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.01.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecreatebuf(int m, int n, int k, sparsematrix s)
+    {
+    
+        sparse.sparsecreatebuf(m, n, k, s.innerobj, null);
+    }
+    
+    public static void sparsecreatebuf(int m, int n, int k, sparsematrix s, alglib.xparams _params)
+    {
+    
+        sparse.sparsecreatebuf(m, n, k, s.innerobj, _params);
+    }
+            
+    public static void sparsecreatebuf(int m, int n, sparsematrix s)
+    {
+        int k;
+    
+    
+        k = 0;
+        sparse.sparsecreatebuf(m, n, k, s.innerobj, null);
+    
+        return;
+    }
+            
+    public static void sparsecreatebuf(int m, int n, sparsematrix s, alglib.xparams _params)
+    {
+        int k;
+    
+    
+        k = 0;
+        sparse.sparsecreatebuf(m, n, k, s.innerobj, _params);
+    
+        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
+    in comments on SparseMatrix structure.  We recommend  you  to  read  them
+    before starting to use ALGLIB sparse matrices..
+
+    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: this function completely  overwrites  S  with  new  sparse  matrix.
+          Previously allocated storage is NOT reused. If you  want  to  reuse
+          already allocated memory, call SparseCreateCRSBuf function.
+
+      -- 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, null);
+    }
+    
+    public static void sparsecreatecrs(int m, int n, int[] ner, out sparsematrix s, alglib.xparams _params)
+    {
+        s = new sparsematrix();
+        sparse.sparsecreatecrs(m, n, ner, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function creates sparse matrix in a CRS format (expert function  for
+    situations when you are running out  of  memory).  This  version  of  CRS
+    matrix creation function may reuse memory already allocated in S.
+
+    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
+    in comments on SparseMatrix structure.  We recommend  you  to  read  them
+    before starting to use ALGLIB sparse matrices..
+
+    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
+        S           -   sparse matrix structure with possibly preallocated
+                        memory.
+
+    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.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecreatecrsbuf(int m, int n, int[] ner, sparsematrix s)
+    {
+    
+        sparse.sparsecreatecrsbuf(m, n, ner, s.innerobj, null);
+    }
+    
+    public static void sparsecreatecrsbuf(int m, int n, int[] ner, sparsematrix s, alglib.xparams _params)
+    {
+    
+        sparse.sparsecreatecrsbuf(m, n, ner, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function creates sparse matrix in  a  SKS  format  (skyline  storage
+    format). In most cases you do not need this function - CRS format  better
+    suits most use cases.
+
+    INPUT PARAMETERS
+        M, N        -   number of rows(M) and columns (N) in a matrix:
+                        * M=N (as for now, ALGLIB supports only square SKS)
+                        * N>=1
+                        * M>=1
+        D           -   "bottom" bandwidths, array[M], D[I]>=0.
+                        I-th element stores number of non-zeros at I-th  row,
+                        below the diagonal (diagonal itself is not  included)
+        U           -   "top" bandwidths, array[N], U[I]>=0.
+                        I-th element stores number of non-zeros  at I-th row,
+                        above the diagonal (diagonal itself  is not included)
+
+    OUTPUT PARAMETERS
+        S           -   sparse M*N matrix in SKS representation.
+                        All elements are filled by zeros.
+                        You may use sparseset() to change their values.
+
+    NOTE: this function completely  overwrites  S  with  new  sparse  matrix.
+          Previously allocated storage is NOT reused. If you  want  to  reuse
+          already allocated memory, call SparseCreateSKSBuf function.
+
+      -- ALGLIB PROJECT --
+         Copyright 13.01.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecreatesks(int m, int n, int[] d, int[] u, out sparsematrix s)
+    {
+        s = new sparsematrix();
+        sparse.sparsecreatesks(m, n, d, u, s.innerobj, null);
+    }
+    
+    public static void sparsecreatesks(int m, int n, int[] d, int[] u, out sparsematrix s, alglib.xparams _params)
+    {
+        s = new sparsematrix();
+        sparse.sparsecreatesks(m, n, d, u, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This is "buffered"  version  of  SparseCreateSKS()  which  reuses  memory
+    previously allocated in S (of course, memory is reallocated if needed).
+
+    This function creates sparse matrix in  a  SKS  format  (skyline  storage
+    format). In most cases you do not need this function - CRS format  better
+    suits most use cases.
+
+    INPUT PARAMETERS
+        M, N        -   number of rows(M) and columns (N) in a matrix:
+                        * M=N (as for now, ALGLIB supports only square SKS)
+                        * N>=1
+                        * M>=1
+        D           -   "bottom" bandwidths, array[M], 0<=D[I]<=I.
+                        I-th element stores number of non-zeros at I-th row,
+                        below the diagonal (diagonal itself is not included)
+        U           -   "top" bandwidths, array[N], 0<=U[I]<=I.
+                        I-th element stores number of non-zeros at I-th row,
+                        above the diagonal (diagonal itself is not included)
+
+    OUTPUT PARAMETERS
+        S           -   sparse M*N matrix in SKS representation.
+                        All elements are filled by zeros.
+                        You may use sparseset() to change their values.
+
+      -- ALGLIB PROJECT --
+         Copyright 13.01.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecreatesksbuf(int m, int n, int[] d, int[] u, sparsematrix s)
+    {
+    
+        sparse.sparsecreatesksbuf(m, n, d, u, s.innerobj, null);
+    }
+    
+    public static void sparsecreatesksbuf(int m, int n, int[] d, int[] u, sparsematrix s, alglib.xparams _params)
+    {
+    
+        sparse.sparsecreatesksbuf(m, n, d, u, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function creates sparse matrix in  a  SKS  format  (skyline  storage
+    format). Unlike more general  sparsecreatesks(),  this  function  creates
+    sparse matrix with constant bandwidth.
+
+    You may want to use this function instead of sparsecreatesks() when  your
+    matrix has  constant  or  nearly-constant  bandwidth,  and  you  want  to
+    simplify source code.
+
+    INPUT PARAMETERS
+        M, N        -   number of rows(M) and columns (N) in a matrix:
+                        * M=N (as for now, ALGLIB supports only square SKS)
+                        * N>=1
+                        * M>=1
+        BW          -   matrix bandwidth, BW>=0
+
+    OUTPUT PARAMETERS
+        S           -   sparse M*N matrix in SKS representation.
+                        All elements are filled by zeros.
+                        You may use sparseset() to  change  their values.
+
+    NOTE: this function completely  overwrites  S  with  new  sparse  matrix.
+          Previously allocated storage is NOT reused. If you  want  to  reuse
+          already allocated memory, call sparsecreatesksbandbuf function.
+
+      -- ALGLIB PROJECT --
+         Copyright 25.12.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecreatesksband(int m, int n, int bw, out sparsematrix s)
+    {
+        s = new sparsematrix();
+        sparse.sparsecreatesksband(m, n, bw, s.innerobj, null);
+    }
+    
+    public static void sparsecreatesksband(int m, int n, int bw, out sparsematrix s, alglib.xparams _params)
+    {
+        s = new sparsematrix();
+        sparse.sparsecreatesksband(m, n, bw, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This is "buffered" version  of  sparsecreatesksband() which reuses memory
+    previously allocated in S (of course, memory is reallocated if needed).
+
+    You may want to use this function instead  of  sparsecreatesksbuf()  when
+    your matrix has  constant or nearly-constant  bandwidth,  and you want to
+    simplify source code.
+
+    INPUT PARAMETERS
+        M, N        -   number of rows(M) and columns (N) in a matrix:
+                        * M=N (as for now, ALGLIB supports only square SKS)
+                        * N>=1
+                        * M>=1
+        BW          -   bandwidth, BW>=0
+
+    OUTPUT PARAMETERS
+        S           -   sparse M*N matrix in SKS representation.
+                        All elements are filled by zeros.
+                        You may use sparseset() to change their values.
+
+      -- ALGLIB PROJECT --
+         Copyright 13.01.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecreatesksbandbuf(int m, int n, int bw, sparsematrix s)
+    {
+    
+        sparse.sparsecreatesksbandbuf(m, n, bw, s.innerobj, null);
+    }
+    
+    public static void sparsecreatesksbandbuf(int m, int n, int bw, sparsematrix s, alglib.xparams _params)
+    {
+    
+        sparse.sparsecreatesksbandbuf(m, n, bw, s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function copies S0 to S1.
+    This function completely deallocates memory owned by S1 before creating a
+    copy of S0. If you want to reuse memory, use SparseCopyBuf.
+
+    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, null);
+    }
+    
+    public static void sparsecopy(sparsematrix s0, out sparsematrix s1, alglib.xparams _params)
+    {
+        s1 = new sparsematrix();
+        sparse.sparsecopy(s0.innerobj, s1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function copies S0 to S1.
+    Memory already allocated in S1 is reused as much as possible.
+
+    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 sparsecopybuf(sparsematrix s0, sparsematrix s1)
+    {
+    
+        sparse.sparsecopybuf(s0.innerobj, s1.innerobj, null);
+    }
+    
+    public static void sparsecopybuf(sparsematrix s0, sparsematrix s1, alglib.xparams _params)
+    {
+    
+        sparse.sparsecopybuf(s0.innerobj, s1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function efficiently swaps contents of S0 and S1.
+
+      -- ALGLIB PROJECT --
+         Copyright 16.01.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparseswap(sparsematrix s0, sparsematrix s1)
+    {
+    
+        sparse.sparseswap(s0.innerobj, s1.innerobj, null);
+    }
+    
+    public static void sparseswap(sparsematrix s0, sparsematrix s1, alglib.xparams _params)
+    {
+    
+        sparse.sparseswap(s0.innerobj, s1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void sparseadd(sparsematrix s, int i, int j, double v, alglib.xparams _params)
+    {
+    
+        sparse.sparseadd(s.innerobj, i, j, v, _params);
+    }
+    
+    /*************************************************************************
+    This function modifies S[i,j] - element of the sparse matrix.
+
+    For Hash-based storage format:
+    * this function can be called at any moment - during matrix initialization
+      or later
+    * new value can be zero or non-zero.  In case new value of S[i,j] is zero,
+      this element is deleted from the table.
+    * this  function  has  no  effect when called with zero V for non-existent
+      element.
+
+    For CRS-bases storage format:
+    * this function can be called ONLY DURING MATRIX INITIALIZATION
+    * zero values are stored in the matrix similarly to non-zero ones
+    * elements must be initialized in correct order -  from top row to bottom,
+      within row - from left to right.
+
+    For SKS storage:
+    * this function can be called at any moment - during matrix initialization
+      or later
+    * zero values are stored in the matrix similarly to non-zero ones
+    * this function CAN NOT be called for non-existent (outside  of  the  band
+      specified during SKS matrix creation) elements. Say, if you created  SKS
+      matrix  with  bandwidth=2  and  tried to call sparseset(s,0,10,VAL),  an
+      exception will be generated.
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in Hash-Table, SKS or CRS format.
+        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
+
+      -- 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, null);
+    }
+    
+    public static void sparseset(sparsematrix s, int i, int j, double v, alglib.xparams _params)
+    {
+    
+        sparse.sparseset(s.innerobj, i, j, v, _params);
+    }
+    
+    /*************************************************************************
+    This function returns S[i,j] - element of the sparse matrix.  Matrix  can
+    be in any mode (Hash-Table, CRS, SKS), but this function is less efficient
+    for CRS matrices. Hash-Table and SKS matrices can find  element  in  O(1)
+    time, while  CRS  matrices need O(log(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)
+    {
+    
+        return sparse.sparseget(s.innerobj, i, j, null);
+    }
+    
+    public static double sparseget(sparsematrix s, int i, int j, alglib.xparams _params)
+    {
+    
+        return sparse.sparseget(s.innerobj, i, j, _params);
+    }
+    
+    /*************************************************************************
+    This function returns I-th diagonal element of the sparse matrix.
+
+    Matrix can be in any mode (Hash-Table or CRS storage), but this  function
+    is most efficient for CRS matrices - it requires less than 50 CPU  cycles
+    to extract diagonal element. For Hash-Table matrices we still  have  O(1)
+    query time, but function is many times slower.
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in Hash-Table representation.
+                        Exception will be thrown for CRS matrix.
+        I           -   index of the element to modify, 0<=I<min(M,N)
+
+    RESULT
+        value of S[I,I] or zero (in case no element with such index is found)
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static double sparsegetdiagonal(sparsematrix s, int i)
+    {
+    
+        return sparse.sparsegetdiagonal(s.innerobj, i, null);
+    }
+    
+    public static double sparsegetdiagonal(sparsematrix s, int i, alglib.xparams _params)
+    {
+    
+        return sparse.sparsegetdiagonal(s.innerobj, i, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates matrix-vector product  S*x.  Matrix  S  must  be
+    stored in CRS or SKS format (exception will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in CRS or SKS format.
+        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/SKS  matrix.
+    You must convert your matrix with SparseConvertToCRS/SKS()  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, null);
+    }
+    
+    public static void sparsemv(sparsematrix s, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        sparse.sparsemv(s.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates matrix-vector product  S^T*x. Matrix S  must  be
+    stored in CRS or SKS format (exception will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in CRS or SKS format.
+        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/SKS  matrix.
+    You must convert your matrix with SparseConvertToCRS/SKS()  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, null);
+    }
+    
+    public static void sparsemtv(sparsematrix s, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        sparse.sparsemtv(s.innerobj, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function simultaneously calculates two matrix-vector products:
+        S*x and S^T*x.
+    S must be square (non-rectangular) matrix stored in  CRS  or  SKS  format
+    (exception will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse N*N matrix in CRS or SKS format.
+        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/SKS  matrix.
+    You must convert your matrix with SparseConvertToCRS/SKS()  before  using
+    this function.
+
+      -- 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, null);
+    }
+    
+    public static void sparsemv2(sparsematrix s, double[] x, ref double[] y0, ref double[] y1, alglib.xparams _params)
+    {
+    
+        sparse.sparsemv2(s.innerobj, x, ref y0, ref y1, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates matrix-vector product  S*x, when S is  symmetric
+    matrix. Matrix S  must be stored in CRS or SKS format  (exception will be
+    thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*M matrix in CRS or SKS format.
+        IsUpper     -   whether upper or lower triangle of S is given:
+                        * if upper triangle is given,  only   S[i,j] for j>=i
+                          are used, and lower triangle is ignored (it can  be
+                          empty - these elements are not referenced at all).
+                        * if lower triangle is given,  only   S[i,j] for j<=i
+                          are used, and upper triangle is ignored.
+        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/SKS  matrix.
+    You must convert your matrix with SparseConvertToCRS/SKS()  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, null);
+    }
+    
+    public static void sparsesmv(sparsematrix s, bool isupper, double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        sparse.sparsesmv(s.innerobj, isupper, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates vector-matrix-vector product x'*S*x, where  S is
+    symmetric matrix. Matrix S must be stored in CRS or SKS format (exception
+    will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*M matrix in CRS or SKS format.
+        IsUpper     -   whether upper or lower triangle of S is given:
+                        * if upper triangle is given,  only   S[i,j] for j>=i
+                          are used, and lower triangle is ignored (it can  be
+                          empty - these elements are not referenced at all).
+                        * if lower triangle is given,  only   S[i,j] for j<=i
+                          are used, and upper triangle is ignored.
+        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.
+
+    RESULT
+        x'*S*x
+
+    NOTE: this function throws exception when called for non-CRS/SKS  matrix.
+    You must convert your matrix with SparseConvertToCRS/SKS()  before  using
+    this function.
+
+      -- ALGLIB PROJECT --
+         Copyright 27.01.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static double sparsevsmv(sparsematrix s, bool isupper, double[] x)
+    {
+    
+        return sparse.sparsevsmv(s.innerobj, isupper, x, null);
+    }
+    
+    public static double sparsevsmv(sparsematrix s, bool isupper, double[] x, alglib.xparams _params)
+    {
+    
+        return sparse.sparsevsmv(s.innerobj, isupper, x, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates matrix-matrix product  S*A.  Matrix  S  must  be
+    stored in CRS or SKS format (exception will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in CRS or SKS format.
+        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/SKS  matrix.
+    You must convert your matrix with SparseConvertToCRS/SKS()  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, null);
+    }
+    
+    public static void sparsemm(sparsematrix s, double[,] a, int k, ref double[,] b, alglib.xparams _params)
+    {
+    
+        sparse.sparsemm(s.innerobj, a, k, ref b, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates matrix-matrix product  S^T*A. Matrix S  must  be
+    stored in CRS or SKS format (exception will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in CRS or SKS format.
+        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/SKS  matrix.
+    You must convert your matrix with SparseConvertToCRS/SKS()  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, null);
+    }
+    
+    public static void sparsemtm(sparsematrix s, double[,] a, int k, ref double[,] b, alglib.xparams _params)
+    {
+    
+        sparse.sparsemtm(s.innerobj, a, k, ref b, _params);
+    }
+    
+    /*************************************************************************
+    This function simultaneously calculates two matrix-matrix products:
+        S*A and S^T*A.
+    S  must  be  square (non-rectangular) matrix stored in CRS or  SKS  format
+    (exception will be thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse N*N matrix in CRS or SKS format.
+        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/SKS  matrix.
+    You must convert your matrix with SparseConvertToCRS/SKS()  before  using
+    this function.
+
+      -- 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, null);
+    }
+    
+    public static void sparsemm2(sparsematrix s, double[,] a, int k, ref double[,] b0, ref double[,] b1, alglib.xparams _params)
+    {
+    
+        sparse.sparsemm2(s.innerobj, a, k, ref b0, ref b1, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates matrix-matrix product  S*A, when S  is  symmetric
+    matrix. Matrix S must be stored in CRS or SKS format  (exception  will  be
+    thrown otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse M*M matrix in CRS or SKS format.
+        IsUpper     -   whether upper or lower triangle of S is given:
+                        * if upper triangle is given,  only   S[i,j] for j>=i
+                          are used, and lower triangle is ignored (it can  be
+                          empty - these elements are not referenced at all).
+                        * if lower triangle is given,  only   S[i,j] for j<=i
+                          are used, and upper triangle is ignored.
+        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/SKS  matrix.
+    You must convert your matrix with SparseConvertToCRS/SKS()  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, null);
+    }
+    
+    public static void sparsesmm(sparsematrix s, bool isupper, double[,] a, int k, ref double[,] b, alglib.xparams _params)
+    {
+    
+        sparse.sparsesmm(s.innerobj, isupper, a, k, ref b, _params);
+    }
+    
+    /*************************************************************************
+    This function calculates matrix-vector product op(S)*x, when x is  vector,
+    S is symmetric triangular matrix, op(S) is transposition or no  operation.
+    Matrix S must be stored in CRS or SKS format  (exception  will  be  thrown
+    otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse square matrix in CRS or SKS format.
+        IsUpper     -   whether upper or lower triangle of S is used:
+                        * if upper triangle is given,  only   S[i,j] for  j>=i
+                          are used, and lower triangle is  ignored (it can  be
+                          empty - these elements are not referenced at all).
+                        * if lower triangle is given,  only   S[i,j] for  j<=i
+                          are used, and upper triangle is ignored.
+        IsUnit      -   unit or non-unit diagonal:
+                        * if True, diagonal elements of triangular matrix  are
+                          considered equal to 1.0. Actual elements  stored  in
+                          S are not referenced at all.
+                        * if False, diagonal stored in S is used
+        OpType      -   operation type:
+                        * if 0, S*x is calculated
+                        * if 1, (S^T)*x is calculated (transposition)
+        X           -   array[N] which stores 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           -   possibly  preallocated  input   buffer.  Automatically
+                        resized if its size is too small.
+
+    OUTPUT PARAMETERS
+        Y           -   array[N], op(S)*x
+
+    NOTE: this function throws exception when called for non-CRS/SKS  matrix.
+    You must convert your matrix with SparseConvertToCRS/SKS()  before  using
+    this function.
+
+      -- ALGLIB PROJECT --
+         Copyright 20.01.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsetrmv(sparsematrix s, bool isupper, bool isunit, int optype, ref double[] x, ref double[] y)
+    {
+    
+        sparse.sparsetrmv(s.innerobj, isupper, isunit, optype, x, ref y, null);
+    }
+    
+    public static void sparsetrmv(sparsematrix s, bool isupper, bool isunit, int optype, ref double[] x, ref double[] y, alglib.xparams _params)
+    {
+    
+        sparse.sparsetrmv(s.innerobj, isupper, isunit, optype, x, ref y, _params);
+    }
+    
+    /*************************************************************************
+    This function solves linear system op(S)*y=x  where  x  is  vector,  S  is
+    symmetric  triangular  matrix,  op(S)  is  transposition  or no operation.
+    Matrix S must be stored in CRS or SKS format  (exception  will  be  thrown
+    otherwise).
+
+    INPUT PARAMETERS
+        S           -   sparse square matrix in CRS or SKS format.
+        IsUpper     -   whether upper or lower triangle of S is used:
+                        * if upper triangle is given,  only   S[i,j] for  j>=i
+                          are used, and lower triangle is  ignored (it can  be
+                          empty - these elements are not referenced at all).
+                        * if lower triangle is given,  only   S[i,j] for  j<=i
+                          are used, and upper triangle is ignored.
+        IsUnit      -   unit or non-unit diagonal:
+                        * if True, diagonal elements of triangular matrix  are
+                          considered equal to 1.0. Actual elements  stored  in
+                          S are not referenced at all.
+                        * if False, diagonal stored in S is used. It  is  your
+                          responsibility  to  make  sure  that   diagonal   is
+                          non-zero.
+        OpType      -   operation type:
+                        * if 0, S*x is calculated
+                        * if 1, (S^T)*x is calculated (transposition)
+        X           -   array[N] which stores 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.
+
+    OUTPUT PARAMETERS
+        X           -   array[N], inv(op(S))*x
+
+    NOTE: this function throws exception when called for  non-CRS/SKS  matrix.
+          You must convert your matrix  with  SparseConvertToCRS/SKS()  before
+          using this function.
+
+    NOTE: no assertion or tests are done during algorithm  operation.   It  is
+          your responsibility to provide invertible matrix to algorithm.
+
+      -- ALGLIB PROJECT --
+         Copyright 20.01.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsetrsv(sparsematrix s, bool isupper, bool isunit, int optype, ref double[] x)
+    {
+    
+        sparse.sparsetrsv(s.innerobj, isupper, isunit, optype, x, null);
+    }
+    
+    public static void sparsetrsv(sparsematrix s, bool isupper, bool isunit, int optype, ref double[] x, alglib.xparams _params)
+    {
+    
+        sparse.sparsetrsv(s.innerobj, isupper, isunit, optype, x, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void sparseresizematrix(sparsematrix s, alglib.xparams _params)
+    {
+    
+        sparse.sparseresizematrix(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function  is  used  to enumerate all elements of the sparse matrix.
+    Before  first  call  user  initializes  T0 and T1 counters by zero. These
+    counters are used to remember current position in a  matrix;  after  each
+    call they are updated by the function.
+
+    Subsequent calls to this function return non-zero elements of the  sparse
+    matrix, one by one. If you enumerate CRS matrix, matrix is traversed from
+    left to right, from top to bottom. In case you enumerate matrix stored as
+    Hash table, elements are returned in random order.
+
+    EXAMPLE
+        > T0=0
+        > T1=0
+        > while SparseEnumerate(S,T0,T1,I,J,V) do
+        >     ....do something with I,J,V
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in Hash-Table or CRS representation.
+        T0          -   internal counter
+        T1          -   internal counter
+
+    OUTPUT PARAMETERS
+        T0          -   new value of the internal counter
+        T1          -   new value of the internal counter
+        I           -   row index of non-zero element, 0<=I<M.
+        J           -   column index of non-zero element, 0<=J<N
+        V           -   value of the T-th element
+
+    RESULT
+        True in case of success (next non-zero element was retrieved)
+        False in case all non-zero elements were enumerated
+
+    NOTE: you may call SparseRewriteExisting() during enumeration, but it  is
+          THE  ONLY  matrix  modification  function  you  can  call!!!  Other
+          matrix modification functions should not be called during enumeration!
+
+      -- ALGLIB PROJECT --
+         Copyright 14.03.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool sparseenumerate(sparsematrix s, ref int t0, ref int t1, out int i, out int j, out double v)
+    {
+        i = 0;
+        j = 0;
+        v = 0;
+        return sparse.sparseenumerate(s.innerobj, ref t0, ref t1, ref i, ref j, ref v, null);
+    }
+    
+    public static bool sparseenumerate(sparsematrix s, ref int t0, ref int t1, out int i, out int j, out double v, alglib.xparams _params)
+    {
+        i = 0;
+        j = 0;
+        v = 0;
+        return sparse.sparseenumerate(s.innerobj, ref t0, ref t1, ref i, ref j, ref v, _params);
+    }
+    
+    /*************************************************************************
+    This function rewrites existing (non-zero) element. It  returns  True   if
+    element  exists  or  False,  when  it  is  called for non-existing  (zero)
+    element.
+
+    This function works with any kind of the matrix.
+
+    The purpose of this function is to provide convenient thread-safe  way  to
+    modify  sparse  matrix.  Such  modification  (already  existing element is
+    rewritten) is guaranteed to be thread-safe without any synchronization, as
+    long as different threads modify different elements.
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in any kind of representation
+                        (Hash, SKS, CRS).
+        I           -   row index of non-zero element to modify, 0<=I<M
+        J           -   column index of non-zero element to modify, 0<=J<N
+        V           -   value to rewrite, must be finite number
+
+    OUTPUT PARAMETERS
+        S           -   modified matrix
+    RESULT
+        True in case when element exists
+        False in case when element doesn't exist or it is zero
+
+      -- ALGLIB PROJECT --
+         Copyright 14.03.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool sparserewriteexisting(sparsematrix s, int i, int j, double v)
+    {
+    
+        return sparse.sparserewriteexisting(s.innerobj, i, j, v, null);
+    }
+    
+    public static bool sparserewriteexisting(sparsematrix s, int i, int j, double v, alglib.xparams _params)
+    {
+    
+        return sparse.sparserewriteexisting(s.innerobj, i, j, v, _params);
+    }
+    
+    /*************************************************************************
+    This function returns I-th row of the sparse matrix. Matrix must be stored
+    in CRS or SKS format.
+
+    INPUT PARAMETERS:
+        S           -   sparse M*N matrix in CRS format
+        I           -   row index, 0<=I<M
+        IRow        -   output buffer, can be  preallocated.  In  case  buffer
+                        size  is  too  small  to  store  I-th   row,   it   is
+                        automatically reallocated.
+
+    OUTPUT PARAMETERS:
+        IRow        -   array[M], I-th row.
+
+    NOTE: this function has O(N) running time, where N is a  column  count. It
+          allocates and fills N-element  array,  even  although  most  of  its
+          elemets are zero.
+
+    NOTE: If you have O(non-zeros-per-row) time and memory  requirements,  use
+          SparseGetCompressedRow() function. It  returns  data  in  compressed
+          format.
+
+    NOTE: when  incorrect  I  (outside  of  [0,M-1]) or  matrix (non  CRS/SKS)
+          is passed, this function throws exception.
+
+      -- ALGLIB PROJECT --
+         Copyright 10.12.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsegetrow(sparsematrix s, int i, ref double[] irow)
+    {
+    
+        sparse.sparsegetrow(s.innerobj, i, ref irow, null);
+    }
+    
+    public static void sparsegetrow(sparsematrix s, int i, ref double[] irow, alglib.xparams _params)
+    {
+    
+        sparse.sparsegetrow(s.innerobj, i, ref irow, _params);
+    }
+    
+    /*************************************************************************
+    This function returns I-th row of the sparse matrix IN COMPRESSED FORMAT -
+    only non-zero elements are returned (with their indexes). Matrix  must  be
+    stored in CRS or SKS format.
+
+    INPUT PARAMETERS:
+        S           -   sparse M*N matrix in CRS format
+        I           -   row index, 0<=I<M
+        ColIdx      -   output buffer for column indexes, can be preallocated.
+                        In case buffer size is too small to store I-th row, it
+                        is automatically reallocated.
+        Vals        -   output buffer for values, can be preallocated. In case
+                        buffer size is too small to  store  I-th  row,  it  is
+                        automatically reallocated.
+
+    OUTPUT PARAMETERS:
+        ColIdx      -   column   indexes   of  non-zero  elements,  sorted  by
+                        ascending. Symbolically non-zero elements are  counted
+                        (i.e. if you allocated place for element, but  it  has
+                        zero numerical value - it is counted).
+        Vals        -   values. Vals[K] stores value of  matrix  element  with
+                        indexes (I,ColIdx[K]). Symbolically non-zero  elements
+                        are counted (i.e. if you allocated place for  element,
+                        but it has zero numerical value - it is counted).
+        NZCnt       -   number of symbolically non-zero elements per row.
+
+    NOTE: when  incorrect  I  (outside  of  [0,M-1]) or  matrix (non  CRS/SKS)
+          is passed, this function throws exception.
+
+    NOTE: this function may allocate additional, unnecessary place for  ColIdx
+          and Vals arrays. It is dictated by  performance  reasons  -  on  SKS
+          matrices it is faster  to  allocate  space  at  the  beginning  with
+          some "extra"-space, than performing two passes over matrix  -  first
+          time to calculate exact space required for data, second  time  -  to
+          store data itself.
+
+      -- ALGLIB PROJECT --
+         Copyright 10.12.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsegetcompressedrow(sparsematrix s, int i, ref int[] colidx, ref double[] vals, out int nzcnt)
+    {
+        nzcnt = 0;
+        sparse.sparsegetcompressedrow(s.innerobj, i, ref colidx, ref vals, ref nzcnt, null);
+    }
+    
+    public static void sparsegetcompressedrow(sparsematrix s, int i, ref int[] colidx, ref double[] vals, out int nzcnt, alglib.xparams _params)
+    {
+        nzcnt = 0;
+        sparse.sparsegetcompressedrow(s.innerobj, i, ref colidx, ref vals, ref nzcnt, _params);
+    }
+    
+    /*************************************************************************
+    This function performs efficient in-place  transpose  of  SKS  matrix.  No
+    additional memory is allocated during transposition.
+
+    This function supports only skyline storage format (SKS).
+
+    INPUT PARAMETERS
+        S       -   sparse matrix in SKS format.
+
+    OUTPUT PARAMETERS
+        S           -   sparse matrix, transposed.
+
+      -- ALGLIB PROJECT --
+         Copyright 16.01.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsetransposesks(sparsematrix s)
+    {
+    
+        sparse.sparsetransposesks(s.innerobj, null);
+    }
+    
+    public static void sparsetransposesks(sparsematrix s, alglib.xparams _params)
+    {
+    
+        sparse.sparsetransposesks(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function performs transpose of CRS matrix.
+
+    INPUT PARAMETERS
+        S       -   sparse matrix in CRS format.
+
+    OUTPUT PARAMETERS
+        S           -   sparse matrix, transposed.
+
+    NOTE: internal  temporary  copy  is  allocated   for   the   purposes   of
+          transposition. It is deallocated after transposition.
+
+      -- ALGLIB PROJECT --
+         Copyright 30.01.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsetransposecrs(sparsematrix s)
+    {
+    
+        sparse.sparsetransposecrs(s.innerobj, null);
+    }
+    
+    public static void sparsetransposecrs(sparsematrix s, alglib.xparams _params)
+    {
+    
+        sparse.sparsetransposecrs(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function performs copying with transposition of CRS matrix.
+
+    INPUT PARAMETERS
+        S0      -   sparse matrix in CRS format.
+
+    OUTPUT PARAMETERS
+        S1      -   sparse matrix, transposed
+
+      -- ALGLIB PROJECT --
+         Copyright 23.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecopytransposecrs(sparsematrix s0, out sparsematrix s1)
+    {
+        s1 = new sparsematrix();
+        sparse.sparsecopytransposecrs(s0.innerobj, s1.innerobj, null);
+    }
+    
+    public static void sparsecopytransposecrs(sparsematrix s0, out sparsematrix s1, alglib.xparams _params)
+    {
+        s1 = new sparsematrix();
+        sparse.sparsecopytransposecrs(s0.innerobj, s1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function performs copying with transposition of CRS matrix  (buffered
+    version which reuses memory already allocated by  the  target as  much  as
+    possible).
+
+    INPUT PARAMETERS
+        S0      -   sparse matrix in CRS format.
+
+    OUTPUT PARAMETERS
+        S1      -   sparse matrix, transposed; previously allocated memory  is
+                    reused if possible.
+
+      -- ALGLIB PROJECT --
+         Copyright 23.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecopytransposecrsbuf(sparsematrix s0, sparsematrix s1)
+    {
+    
+        sparse.sparsecopytransposecrsbuf(s0.innerobj, s1.innerobj, null);
+    }
+    
+    public static void sparsecopytransposecrsbuf(sparsematrix s0, sparsematrix s1, alglib.xparams _params)
+    {
+    
+        sparse.sparsecopytransposecrsbuf(s0.innerobj, s1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function  performs  in-place  conversion  to  desired sparse storage
+    format.
+
+    INPUT PARAMETERS
+        S0      -   sparse matrix in any format.
+        Fmt     -   desired storage format  of  the  output,  as  returned  by
+                    SparseGetMatrixType() function:
+                    * 0 for hash-based storage
+                    * 1 for CRS
+                    * 2 for SKS
+
+    OUTPUT PARAMETERS
+        S0          -   sparse matrix in requested format.
+
+    NOTE: in-place conversion wastes a lot of memory which is  used  to  store
+          temporaries.  If  you  perform  a  lot  of  repeated conversions, we
+          recommend to use out-of-place buffered  conversion  functions,  like
+          SparseCopyToBuf(), which can reuse already allocated memory.
+
+      -- ALGLIB PROJECT --
+         Copyright 16.01.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparseconvertto(sparsematrix s0, int fmt)
+    {
+    
+        sparse.sparseconvertto(s0.innerobj, fmt, null);
+    }
+    
+    public static void sparseconvertto(sparsematrix s0, int fmt, alglib.xparams _params)
+    {
+    
+        sparse.sparseconvertto(s0.innerobj, fmt, _params);
+    }
+    
+    /*************************************************************************
+    This  function  performs out-of-place conversion to desired sparse storage
+    format. S0 is copied to S1 and converted on-the-fly. Memory  allocated  in
+    S1 is reused to maximum extent possible.
+
+    INPUT PARAMETERS
+        S0      -   sparse matrix in any format.
+        Fmt     -   desired storage format  of  the  output,  as  returned  by
+                    SparseGetMatrixType() function:
+                    * 0 for hash-based storage
+                    * 1 for CRS
+                    * 2 for SKS
+
+    OUTPUT PARAMETERS
+        S1          -   sparse matrix in requested format.
+
+      -- ALGLIB PROJECT --
+         Copyright 16.01.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecopytobuf(sparsematrix s0, int fmt, sparsematrix s1)
+    {
+    
+        sparse.sparsecopytobuf(s0.innerobj, fmt, s1.innerobj, null);
+    }
+    
+    public static void sparsecopytobuf(sparsematrix s0, int fmt, sparsematrix s1, alglib.xparams _params)
+    {
+    
+        sparse.sparsecopytobuf(s0.innerobj, fmt, s1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function performs in-place conversion to Hash table storage.
+
+    INPUT PARAMETERS
+        S           -   sparse matrix in CRS format.
+
+    OUTPUT PARAMETERS
+        S           -   sparse matrix in Hash table format.
+
+    NOTE: this  function  has   no  effect  when  called with matrix which  is
+          already in Hash table mode.
+
+    NOTE: in-place conversion involves allocation of temporary arrays. If  you
+          perform a lot of repeated in- place  conversions,  it  may  lead  to
+          memory fragmentation. Consider using out-of-place SparseCopyToHashBuf()
+          function in this case.
+
+      -- ALGLIB PROJECT --
+         Copyright 20.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparseconverttohash(sparsematrix s)
+    {
+    
+        sparse.sparseconverttohash(s.innerobj, null);
+    }
+    
+    public static void sparseconverttohash(sparsematrix s, alglib.xparams _params)
+    {
+    
+        sparse.sparseconverttohash(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function  performs  out-of-place  conversion  to  Hash table storage
+    format. S0 is copied to S1 and converted on-the-fly.
+
+    INPUT PARAMETERS
+        S0          -   sparse matrix in any format.
+
+    OUTPUT PARAMETERS
+        S1          -   sparse matrix in Hash table format.
+
+    NOTE: if S0 is stored as Hash-table, it is just copied without conversion.
+
+    NOTE: this function de-allocates memory  occupied  by  S1 before  starting
+          conversion. If you perform a  lot  of  repeated  conversions, it may
+          lead to memory fragmentation. In this case we recommend you  to  use
+          SparseCopyToHashBuf() function which re-uses memory in S1 as much as
+          possible.
+
+      -- ALGLIB PROJECT --
+         Copyright 20.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecopytohash(sparsematrix s0, out sparsematrix s1)
+    {
+        s1 = new sparsematrix();
+        sparse.sparsecopytohash(s0.innerobj, s1.innerobj, null);
+    }
+    
+    public static void sparsecopytohash(sparsematrix s0, out sparsematrix s1, alglib.xparams _params)
+    {
+        s1 = new sparsematrix();
+        sparse.sparsecopytohash(s0.innerobj, s1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function  performs  out-of-place  conversion  to  Hash table storage
+    format. S0 is copied to S1 and converted on-the-fly. Memory  allocated  in
+    S1 is reused to maximum extent possible.
+
+    INPUT PARAMETERS
+        S0          -   sparse matrix in any format.
+
+    OUTPUT PARAMETERS
+        S1          -   sparse matrix in Hash table format.
+
+    NOTE: if S0 is stored as Hash-table, it is just copied without conversion.
+
+      -- ALGLIB PROJECT --
+         Copyright 20.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecopytohashbuf(sparsematrix s0, sparsematrix s1)
+    {
+    
+        sparse.sparsecopytohashbuf(s0.innerobj, s1.innerobj, null);
+    }
+    
+    public static void sparsecopytohashbuf(sparsematrix s0, sparsematrix s1, alglib.xparams _params)
+    {
+    
+        sparse.sparsecopytohashbuf(s0.innerobj, s1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function converts matrix to CRS format.
+
+    Some  algorithms  (linear  algebra ones, for example) require matrices in
+    CRS format. This function allows to perform in-place conversion.
+
+    INPUT PARAMETERS
+        S           -   sparse M*N matrix in any format
+
+    OUTPUT PARAMETERS
+        S           -   matrix in CRS format
+
+    NOTE: this   function  has  no  effect  when  called with matrix which is
+          already in CRS mode.
+
+    NOTE: this function allocates temporary memory to store a   copy  of  the
+          matrix. If you perform a lot of repeated conversions, we  recommend
+          you  to  use  SparseCopyToCRSBuf()  function,   which   can   reuse
+          previously allocated memory.
+
+      -- ALGLIB PROJECT --
+         Copyright 14.10.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparseconverttocrs(sparsematrix s)
+    {
+    
+        sparse.sparseconverttocrs(s.innerobj, null);
+    }
+    
+    public static void sparseconverttocrs(sparsematrix s, alglib.xparams _params)
+    {
+    
+        sparse.sparseconverttocrs(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function  performs  out-of-place  conversion  to  CRS format.  S0 is
+    copied to S1 and converted on-the-fly.
+
+    INPUT PARAMETERS
+        S0          -   sparse matrix in any format.
+
+    OUTPUT PARAMETERS
+        S1          -   sparse matrix in CRS format.
+
+    NOTE: if S0 is stored as CRS, it is just copied without conversion.
+
+    NOTE: this function de-allocates memory occupied by S1 before starting CRS
+          conversion. If you perform a lot of repeated CRS conversions, it may
+          lead to memory fragmentation. In this case we recommend you  to  use
+          SparseCopyToCRSBuf() function which re-uses memory in S1 as much  as
+          possible.
+
+      -- ALGLIB PROJECT --
+         Copyright 20.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecopytocrs(sparsematrix s0, out sparsematrix s1)
+    {
+        s1 = new sparsematrix();
+        sparse.sparsecopytocrs(s0.innerobj, s1.innerobj, null);
+    }
+    
+    public static void sparsecopytocrs(sparsematrix s0, out sparsematrix s1, alglib.xparams _params)
+    {
+        s1 = new sparsematrix();
+        sparse.sparsecopytocrs(s0.innerobj, s1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function  performs  out-of-place  conversion  to  CRS format.  S0 is
+    copied to S1 and converted on-the-fly. Memory allocated in S1 is reused to
+    maximum extent possible.
+
+    INPUT PARAMETERS
+        S0          -   sparse matrix in any format.
+        S1          -   matrix which may contain some pre-allocated memory, or
+                        can be just uninitialized structure.
+
+    OUTPUT PARAMETERS
+        S1          -   sparse matrix in CRS format.
+
+    NOTE: if S0 is stored as CRS, it is just copied without conversion.
+
+      -- ALGLIB PROJECT --
+         Copyright 20.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecopytocrsbuf(sparsematrix s0, sparsematrix s1)
+    {
+    
+        sparse.sparsecopytocrsbuf(s0.innerobj, s1.innerobj, null);
+    }
+    
+    public static void sparsecopytocrsbuf(sparsematrix s0, sparsematrix s1, alglib.xparams _params)
+    {
+    
+        sparse.sparsecopytocrsbuf(s0.innerobj, s1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function performs in-place conversion to SKS format.
+
+    INPUT PARAMETERS
+        S           -   sparse matrix in any format.
+
+    OUTPUT PARAMETERS
+        S           -   sparse matrix in SKS format.
+
+    NOTE: this  function  has   no  effect  when  called with matrix which  is
+          already in SKS mode.
+
+    NOTE: in-place conversion involves allocation of temporary arrays. If  you
+          perform a lot of repeated in- place  conversions,  it  may  lead  to
+          memory fragmentation. Consider using out-of-place SparseCopyToSKSBuf()
+          function in this case.
+
+      -- ALGLIB PROJECT --
+         Copyright 15.01.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparseconverttosks(sparsematrix s)
+    {
+    
+        sparse.sparseconverttosks(s.innerobj, null);
+    }
+    
+    public static void sparseconverttosks(sparsematrix s, alglib.xparams _params)
+    {
+    
+        sparse.sparseconverttosks(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function  performs  out-of-place  conversion  to SKS storage format.
+    S0 is copied to S1 and converted on-the-fly.
+
+    INPUT PARAMETERS
+        S0          -   sparse matrix in any format.
+
+    OUTPUT PARAMETERS
+        S1          -   sparse matrix in SKS format.
+
+    NOTE: if S0 is stored as SKS, it is just copied without conversion.
+
+    NOTE: this function de-allocates memory  occupied  by  S1 before  starting
+          conversion. If you perform a  lot  of  repeated  conversions, it may
+          lead to memory fragmentation. In this case we recommend you  to  use
+          SparseCopyToSKSBuf() function which re-uses memory in S1 as much  as
+          possible.
+
+      -- ALGLIB PROJECT --
+         Copyright 20.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecopytosks(sparsematrix s0, out sparsematrix s1)
+    {
+        s1 = new sparsematrix();
+        sparse.sparsecopytosks(s0.innerobj, s1.innerobj, null);
+    }
+    
+    public static void sparsecopytosks(sparsematrix s0, out sparsematrix s1, alglib.xparams _params)
+    {
+        s1 = new sparsematrix();
+        sparse.sparsecopytosks(s0.innerobj, s1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function  performs  out-of-place  conversion  to SKS format.  S0  is
+    copied to S1 and converted on-the-fly. Memory  allocated  in S1 is  reused
+    to maximum extent possible.
+
+    INPUT PARAMETERS
+        S0          -   sparse matrix in any format.
+
+    OUTPUT PARAMETERS
+        S1          -   sparse matrix in SKS format.
+
+    NOTE: if S0 is stored as SKS, it is just copied without conversion.
+
+      -- ALGLIB PROJECT --
+         Copyright 20.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecopytosksbuf(sparsematrix s0, sparsematrix s1)
+    {
+    
+        sparse.sparsecopytosksbuf(s0.innerobj, s1.innerobj, null);
+    }
+    
+    public static void sparsecopytosksbuf(sparsematrix s0, sparsematrix s1, alglib.xparams _params)
+    {
+    
+        sparse.sparsecopytosksbuf(s0.innerobj, s1.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function returns type of the matrix storage format.
+
+    INPUT PARAMETERS:
+        S           -   sparse matrix.
+
+    RESULT:
+        sparse storage format used by matrix:
+            0   -   Hash-table
+            1   -   CRS (compressed row storage)
+            2   -   SKS (skyline)
+
+    NOTE: future  versions  of  ALGLIB  may  include additional sparse storage
+          formats.
+
+
+      -- ALGLIB PROJECT --
+         Copyright 20.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static int sparsegetmatrixtype(sparsematrix s)
+    {
+    
+        return sparse.sparsegetmatrixtype(s.innerobj, null);
+    }
+    
+    public static int sparsegetmatrixtype(sparsematrix s, alglib.xparams _params)
+    {
+    
+        return sparse.sparsegetmatrixtype(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function checks matrix storage format and returns True when matrix is
+    stored using Hash table representation.
+
+    INPUT PARAMETERS:
+        S   -   sparse matrix.
+
+    RESULT:
+        True if matrix type is Hash table
+        False if matrix type is not Hash table
+
+      -- ALGLIB PROJECT --
+         Copyright 20.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool sparseishash(sparsematrix s)
+    {
+    
+        return sparse.sparseishash(s.innerobj, null);
+    }
+    
+    public static bool sparseishash(sparsematrix s, alglib.xparams _params)
+    {
+    
+        return sparse.sparseishash(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function checks matrix storage format and returns True when matrix is
+    stored using CRS representation.
+
+    INPUT PARAMETERS:
+        S   -   sparse matrix.
+
+    RESULT:
+        True if matrix type is CRS
+        False if matrix type is not CRS
+
+      -- ALGLIB PROJECT --
+         Copyright 20.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool sparseiscrs(sparsematrix s)
+    {
+    
+        return sparse.sparseiscrs(s.innerobj, null);
+    }
+    
+    public static bool sparseiscrs(sparsematrix s, alglib.xparams _params)
+    {
+    
+        return sparse.sparseiscrs(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function checks matrix storage format and returns True when matrix is
+    stored using SKS representation.
+
+    INPUT PARAMETERS:
+        S   -   sparse matrix.
+
+    RESULT:
+        True if matrix type is SKS
+        False if matrix type is not SKS
+
+      -- ALGLIB PROJECT --
+         Copyright 20.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool sparseissks(sparsematrix s)
+    {
+    
+        return sparse.sparseissks(s.innerobj, null);
+    }
+    
+    public static bool sparseissks(sparsematrix s, alglib.xparams _params)
+    {
+    
+        return sparse.sparseissks(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    The function frees all memory occupied by  sparse  matrix.  Sparse  matrix
+    structure becomes unusable after this call.
+
+    OUTPUT PARAMETERS
+        S   -   sparse matrix to delete
+
+      -- ALGLIB PROJECT --
+         Copyright 24.07.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsefree(out sparsematrix s)
+    {
+        s = new sparsematrix();
+        sparse.sparsefree(s.innerobj, null);
+    }
+    
+    public static void sparsefree(out sparsematrix s, alglib.xparams _params)
+    {
+        s = new sparsematrix();
+        sparse.sparsefree(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    The function returns number of rows of a sparse matrix.
+
+    RESULT: number of rows of a sparse matrix.
+
+      -- ALGLIB PROJECT --
+         Copyright 23.08.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static int sparsegetnrows(sparsematrix s)
+    {
+    
+        return sparse.sparsegetnrows(s.innerobj, null);
+    }
+    
+    public static int sparsegetnrows(sparsematrix s, alglib.xparams _params)
+    {
+    
+        return sparse.sparsegetnrows(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    The function returns number of columns of a sparse matrix.
+
+    RESULT: number of columns of a sparse matrix.
+
+      -- ALGLIB PROJECT --
+         Copyright 23.08.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static int sparsegetncols(sparsematrix s)
+    {
+    
+        return sparse.sparsegetncols(s.innerobj, null);
+    }
+    
+    public static int sparsegetncols(sparsematrix s, alglib.xparams _params)
+    {
+    
+        return sparse.sparsegetncols(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    The function returns number of strictly upper triangular non-zero elements
+    in  the  matrix.  It  counts  SYMBOLICALLY non-zero elements, i.e. entries
+    in the sparse matrix data structure. If some element  has  zero  numerical
+    value, it is still counted.
+
+    This function has different cost for different types of matrices:
+    * for hash-based matrices it involves complete pass over entire hash-table
+      with O(NNZ) cost, where NNZ is number of non-zero elements
+    * for CRS and SKS matrix types cost of counting is O(N) (N - matrix size).
+
+    RESULT: number of non-zero elements strictly above main diagonal
+
+      -- ALGLIB PROJECT --
+         Copyright 12.02.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static int sparsegetuppercount(sparsematrix s)
+    {
+    
+        return sparse.sparsegetuppercount(s.innerobj, null);
+    }
+    
+    public static int sparsegetuppercount(sparsematrix s, alglib.xparams _params)
+    {
+    
+        return sparse.sparsegetuppercount(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    The function returns number of strictly lower triangular non-zero elements
+    in  the  matrix.  It  counts  SYMBOLICALLY non-zero elements, i.e. entries
+    in the sparse matrix data structure. If some element  has  zero  numerical
+    value, it is still counted.
+
+    This function has different cost for different types of matrices:
+    * for hash-based matrices it involves complete pass over entire hash-table
+      with O(NNZ) cost, where NNZ is number of non-zero elements
+    * for CRS and SKS matrix types cost of counting is O(N) (N - matrix size).
+
+    RESULT: number of non-zero elements strictly below main diagonal
+
+      -- ALGLIB PROJECT --
+         Copyright 12.02.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static int sparsegetlowercount(sparsematrix s)
+    {
+    
+        return sparse.sparsegetlowercount(s.innerobj, null);
+    }
+    
+    public static int sparsegetlowercount(sparsematrix s, alglib.xparams _params)
+    {
+    
+        return sparse.sparsegetlowercount(s.innerobj, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void cmatrixtranspose(int m, int n, complex[,] a, int ia, int ja, ref complex[,] b, int ib, int jb, alglib.xparams _params)
+    {
+    
+        ablas.cmatrixtranspose(m, n, a, ia, ja, ref b, ib, jb, _params);
+    }
+    
+    /*************************************************************************
+    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, b, ib, jb, null);
+    }
+    
+    public static void rmatrixtranspose(int m, int n, double[,] a, int ia, int ja, ref double[,] b, int ib, int jb, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixtranspose(m, n, a, ia, ja, b, ib, jb, _params);
+    }
+    
+    /*************************************************************************
+    This code enforces symmetricy of the matrix by copying Upper part to lower
+    one (or vice versa).
+
+    INPUT PARAMETERS:
+        A   -   matrix
+        N   -   number of rows/columns
+        IsUpper - whether we want to copy upper triangle to lower one (True)
+                or vice versa (False).
+    *************************************************************************/
+    public static void rmatrixenforcesymmetricity(ref double[,] a, int n, bool isupper)
+    {
+    
+        ablas.rmatrixenforcesymmetricity(a, n, isupper, null);
+    }
+    
+    public static void rmatrixenforcesymmetricity(ref double[,] a, int n, bool isupper, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixenforcesymmetricity(a, n, isupper, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void cmatrixcopy(int m, int n, complex[,] a, int ia, int ja, ref complex[,] b, int ib, int jb, alglib.xparams _params)
+    {
+    
+        ablas.cmatrixcopy(m, n, a, ia, ja, ref b, ib, jb, _params);
+    }
+    
+    /*************************************************************************
+    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, b, ib, jb, null);
+    }
+    
+    public static void rmatrixcopy(int m, int n, double[,] a, int ia, int ja, ref double[,] b, int ib, int jb, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixcopy(m, n, a, ia, ja, b, ib, jb, _params);
+    }
+    
+    /*************************************************************************
+    Rank-1 correction: A := A + alpha*u*v'
+
+    NOTE: this  function  expects  A  to  be  large enough to store result. No
+          automatic preallocation happens for  smaller  arrays.  No  integrity
+          checks is performed for sizes of 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)
+        Alpha-  coefficient
+        U   -   vector #1
+        IU  -   subvector offset
+        V   -   vector #2
+        IV  -   subvector offset
+
+
+      -- ALGLIB routine --
+
+         16.10.2017
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixger(int m, int n, ref double[,] a, int ia, int ja, double alpha, double[] u, int iu, double[] v, int iv)
+    {
+    
+        ablas.rmatrixger(m, n, a, ia, ja, alpha, u, iu, v, iv, null);
+    }
+    
+    public static void rmatrixger(int m, int n, ref double[,] a, int ia, int ja, double alpha, double[] u, int iu, double[] v, int iv, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixger(m, n, a, ia, ja, alpha, u, iu, v, iv, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        ablas.cmatrixrank1(m, n, ref a, ia, ja, ref u, iu, ref v, iv, _params);
+    }
+    
+    /*************************************************************************
+    IMPORTANT: this function is deprecated since ALGLIB 3.13. Use RMatrixGER()
+               which is more generic version of this function.
+
+    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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixrank1(m, n, ref a, ia, ja, ref u, iu, ref v, iv, _params);
+    }
+    
+    /*************************************************************************
+
+    *************************************************************************/
+    public static void rmatrixgemv(int m, int n, double alpha, double[,] a, int ia, int ja, int opa, double[] x, int ix, double beta, ref double[] y, int iy)
+    {
+    
+        ablas.rmatrixgemv(m, n, alpha, a, ia, ja, opa, x, ix, beta, y, iy, null);
+    }
+    
+    public static void rmatrixgemv(int m, int n, double alpha, double[,] a, int ia, int ja, int opa, double[] x, int ix, double beta, ref double[] y, int iy, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixgemv(m, n, alpha, a, ia, ja, opa, x, ix, beta, y, iy, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        ablas.cmatrixmv(m, n, a, ia, ja, opa, x, ix, ref y, iy, _params);
+    }
+    
+    /*************************************************************************
+    IMPORTANT: this function is deprecated since ALGLIB 3.13. Use RMatrixGEMV()
+               which is more generic version of this function.
+
+    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, y, iy, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixmv(m, n, a, ia, ja, opa, x, ix, y, iy, _params);
+    }
+    
+    /*************************************************************************
+
+    *************************************************************************/
+    public static void rmatrixsymv(int n, double alpha, double[,] a, int ia, int ja, bool isupper, double[] x, int ix, double beta, ref double[] y, int iy)
+    {
+    
+        ablas.rmatrixsymv(n, alpha, a, ia, ja, isupper, x, ix, beta, y, iy, null);
+    }
+    
+    public static void rmatrixsymv(int n, double alpha, double[,] a, int ia, int ja, bool isupper, double[] x, int ix, double beta, ref double[] y, int iy, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixsymv(n, alpha, a, ia, ja, isupper, x, ix, beta, y, iy, _params);
+    }
+    
+    /*************************************************************************
+
+    *************************************************************************/
+    public static double rmatrixsyvmv(int n, double[,] a, int ia, int ja, bool isupper, double[] x, int ix, ref double[] tmp)
+    {
+    
+        return ablas.rmatrixsyvmv(n, a, ia, ja, isupper, x, ix, tmp, null);
+    }
+    
+    public static double rmatrixsyvmv(int n, double[,] a, int ia, int ja, bool isupper, double[] x, int ix, ref double[] tmp, alglib.xparams _params)
+    {
+    
+        return ablas.rmatrixsyvmv(n, a, ia, ja, isupper, x, ix, tmp, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine solves linear system op(A)*x=b where:
+    * A is NxN upper/lower triangular/unitriangular matrix
+    * X and B are Nx1 vectors
+    * "op" may be identity transformation, transposition, conjugate transposition
+
+    Solution replaces X.
+
+    IMPORTANT: * no overflow/underflow/denegeracy tests is performed.
+               * no integrity checks for operand sizes, out-of-bounds accesses
+                 and so on is performed
+
+    INPUT PARAMETERS
+        N   -   matrix size, N>=0
+        A       -   matrix, actial matrix is stored in A[IA:IA+N-1,JA:JA+N-1]
+        IA      -   submatrix offset
+        JA      -   submatrix offset
+        IsUpper -   whether matrix is upper triangular
+        IsUnit  -   whether matrix is unitriangular
+        OpType  -   transformation type:
+                    * 0 - no transformation
+                    * 1 - transposition
+        X       -   right part, actual vector is stored in X[IX:IX+N-1]
+        IX      -   offset
+
+    OUTPUT PARAMETERS
+        X       -   solution replaces elements X[IX:IX+N-1]
+
+      -- ALGLIB routine / remastering of LAPACK's DTRSV --
+         (c) 2017 Bochkanov Sergey - converted to ALGLIB
+         (c) 2016 Reference BLAS level1 routine (LAPACK version 3.7.0)
+         Reference BLAS is a software package provided by Univ. of Tennessee,
+         Univ. of California Berkeley, Univ. of Colorado Denver and NAG Ltd.
+    *************************************************************************/
+    public static void rmatrixtrsv(int n, double[,] a, int ia, int ja, bool isupper, bool isunit, int optype, ref double[] x, int ix)
+    {
+    
+        ablas.rmatrixtrsv(n, a, ia, ja, isupper, isunit, optype, x, ix, null);
+    }
+    
+    public static void rmatrixtrsv(int n, double[,] a, int ia, int ja, bool isupper, bool isunit, int optype, ref double[] x, int ix, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixtrsv(n, a, ia, ja, isupper, isunit, optype, x, ix, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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 --
+         20.01.2018
+         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, x, i2, j2, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        ablas.cmatrixrighttrsm(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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-22.01.2018
+         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, x, i2, j2, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        ablas.cmatrixlefttrsm(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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-22.01.2018
+         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, x, i2, j2, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixrighttrsm(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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-22.01.2018
+         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, x, i2, j2, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixlefttrsm(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+    }
+    
+    /*************************************************************************
+    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:
+    * 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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS
+        N       -   matrix size, N>=0
+        K       -   matrix size, K>=0
+        Alpha   -   coefficient
+        A       -   matrix
+        IA      -   submatrix offset (row index)
+        JA      -   submatrix offset (column index)
+        OpTypeA -   multiplication type:
+                    * 0 - A*A^H is calculated
+                    * 2 - A^H*A is calculated
+        Beta    -   coefficient
+        C       -   preallocated input/output matrix
+        IC      -   submatrix offset (row index)
+        JC      -   submatrix offset (column index)
+        IsUpper -   whether upper or lower triangle of C is updated;
+                    this function updates only one half of C, leaving
+                    other half unchanged (not referenced at all).
+
+      -- ALGLIB routine --
+         16.12.2009-22.01.2018
+         Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixherk(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.cmatrixherk(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, null);
+    }
+    
+    public static void cmatrixherk(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, alglib.xparams _params)
+    {
+    
+        ablas.cmatrixherk(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+    }
+    
+    /*************************************************************************
+    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:
+    * 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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS
+        N       -   matrix size, N>=0
+        K       -   matrix size, K>=0
+        Alpha   -   coefficient
+        A       -   matrix
+        IA      -   submatrix offset (row index)
+        JA      -   submatrix offset (column index)
+        OpTypeA -   multiplication type:
+                    * 0 - A*A^T is calculated
+                    * 2 - A^T*A is calculated
+        Beta    -   coefficient
+        C       -   preallocated input/output matrix
+        IC      -   submatrix offset (row index)
+        JC      -   submatrix offset (column index)
+        IsUpper -   whether C is upper triangular or lower triangular
+
+      -- ALGLIB routine --
+         16.12.2009-22.01.2018
+         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, c, ic, jc, isupper, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixsyrk(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    IMPORTANT:
+
+    This function does NOT preallocate output matrix C, it MUST be preallocated
+    by caller prior to calling this function. In case C does not have  enough
+    space to store result, exception will be generated.
+
+    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 (PREALLOCATED, large enough to store result)
+        IC      -   submatrix offset
+        JC      -   submatrix offset
+
+      -- ALGLIB routine --
+         2009-2019
+         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, c, ic, jc, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        ablas.cmatrixgemm(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    IMPORTANT:
+
+    This function does NOT preallocate output matrix C, it MUST be preallocated
+    by caller prior to calling this function. In case C does not have  enough
+    space to store result, exception will be generated.
+
+    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       -   PREALLOCATED output matrix, large enough to store result
+        IC      -   submatrix offset
+        JC      -   submatrix offset
+
+      -- ALGLIB routine --
+         2009-2019
+         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, c, ic, jc, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        ablas.rmatrixgemm(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine is an older version of CMatrixHERK(), one with wrong  name
+    (it is HErmitian update, not SYmmetric). It  is  left  here  for  backward
+    compatibility.
+
+      -- 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, c, ic, jc, isupper, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        ablas.cmatrixsyrk(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+
+}
+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]
+
+    NOTE: this function uses algorithm  described  in  Stewart, G. W.  (1980),
+          "The Efficient Generation of  Random  Orthogonal  Matrices  with  an
+          Application to Condition Estimators".
+
+          Speaking short, to generate an (N+1)x(N+1) orthogonal matrix, it:
+          * takes an NxN one
+          * takes uniformly distributed unit vector of dimension N+1.
+          * constructs a Householder reflection from the vector, then applies
+            it to the smaller matrix (embedded in the larger size with a 1 at
+            the bottom right corner).
+
+      -- 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, null);
+    }
+    
+    public static void rmatrixrndorthogonal(int n, out double[,] a, alglib.xparams _params)
+    {
+        a = new double[0,0];
+        matgen.rmatrixrndorthogonal(n, ref a, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rmatrixrndcond(int n, double c, out double[,] a, alglib.xparams _params)
+    {
+        a = new double[0,0];
+        matgen.rmatrixrndcond(n, c, ref a, _params);
+    }
+    
+    /*************************************************************************
+    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]
+
+    NOTE: this function uses algorithm  described  in  Stewart, G. W.  (1980),
+          "The Efficient Generation of  Random  Orthogonal  Matrices  with  an
+          Application to Condition Estimators".
+
+          Speaking short, to generate an (N+1)x(N+1) orthogonal matrix, it:
+          * takes an NxN one
+          * takes uniformly distributed unit vector of dimension N+1.
+          * constructs a Householder reflection from the vector, then applies
+            it to the smaller matrix (embedded in the larger size with a 1 at
+            the bottom right corner).
+
+      -- 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, null);
+    }
+    
+    public static void cmatrixrndorthogonal(int n, out complex[,] a, alglib.xparams _params)
+    {
+        a = new complex[0,0];
+        matgen.cmatrixrndorthogonal(n, ref a, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void cmatrixrndcond(int n, double c, out complex[,] a, alglib.xparams _params)
+    {
+        a = new complex[0,0];
+        matgen.cmatrixrndcond(n, c, ref a, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void smatrixrndcond(int n, double c, out double[,] a, alglib.xparams _params)
+    {
+        a = new double[0,0];
+        matgen.smatrixrndcond(n, c, ref a, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void spdmatrixrndcond(int n, double c, out double[,] a, alglib.xparams _params)
+    {
+        a = new double[0,0];
+        matgen.spdmatrixrndcond(n, c, ref a, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void hmatrixrndcond(int n, double c, out complex[,] a, alglib.xparams _params)
+    {
+        a = new complex[0,0];
+        matgen.hmatrixrndcond(n, c, ref a, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void hpdmatrixrndcond(int n, double c, out complex[,] a, alglib.xparams _params)
+    {
+        a = new complex[0,0];
+        matgen.hpdmatrixrndcond(n, c, ref a, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rmatrixrndorthogonalfromtheright(ref double[,] a, int m, int n, alglib.xparams _params)
+    {
+    
+        matgen.rmatrixrndorthogonalfromtheright(ref a, m, n, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rmatrixrndorthogonalfromtheleft(ref double[,] a, int m, int n, alglib.xparams _params)
+    {
+    
+        matgen.rmatrixrndorthogonalfromtheleft(ref a, m, n, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void cmatrixrndorthogonalfromtheright(ref complex[,] a, int m, int n, alglib.xparams _params)
+    {
+    
+        matgen.cmatrixrndorthogonalfromtheright(ref a, m, n, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void cmatrixrndorthogonalfromtheleft(ref complex[,] a, int m, int n, alglib.xparams _params)
+    {
+    
+        matgen.cmatrixrndorthogonalfromtheleft(ref a, m, n, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void smatrixrndmultiply(ref double[,] a, int n, alglib.xparams _params)
+    {
+    
+        matgen.smatrixrndmultiply(ref a, n, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void hmatrixrndmultiply(ref complex[,] a, int n, alglib.xparams _params)
+    {
+    
+        matgen.hmatrixrndmultiply(ref a, n, _params);
+    }
+
+}
+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]
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixlu(ref double[,] a, int m, int n, out int[] pivots, alglib.xparams _params)
+    {
+        pivots = new int[0];
+        trfac.rmatrixlu(ref a, m, n, ref pivots, _params);
+    }
+    
+    /*************************************************************************
+    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]
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void cmatrixlu(ref complex[,] a, int m, int n, out int[] pivots, alglib.xparams _params)
+    {
+        pivots = new int[0];
+        trfac.cmatrixlu(ref a, m, n, ref pivots, _params);
+    }
+    
+    /*************************************************************************
+    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' denotes conj(X^T)).
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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-22.01.2018
+         Bochkanov Sergey
+    *************************************************************************/
+    public static bool hpdmatrixcholesky(ref complex[,] a, int n, bool isupper)
+    {
+    
+        return trfac.hpdmatrixcholesky(ref a, n, isupper, null);
+    }
+    
+    public static bool hpdmatrixcholesky(ref complex[,] a, int n, bool isupper, alglib.xparams _params)
+    {
+    
+        return trfac.hpdmatrixcholesky(ref a, n, isupper, _params);
+    }
+    
+    /*************************************************************************
+    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
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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)
+    {
+    
+        return trfac.spdmatrixcholesky(ref a, n, isupper, null);
+    }
+    
+    public static bool spdmatrixcholesky(ref double[,] a, int n, bool isupper, alglib.xparams _params)
+    {
+    
+        return trfac.spdmatrixcholesky(ref a, n, isupper, _params);
+    }
+    
+    /*************************************************************************
+    Update of Cholesky decomposition: rank-1 update to original A.  "Buffered"
+    version which uses preallocated buffer which is saved  between  subsequent
+    function calls.
+
+    This function uses internally allocated buffer which is not saved  between
+    subsequent  calls.  So,  if  you  perform  a lot  of  subsequent  updates,
+    we  recommend   you   to   use   "buffered"   version   of  this function:
+    SPDMatrixCholeskyUpdateAdd1Buf().
+
+    INPUT PARAMETERS:
+        A       -   upper or lower Cholesky factor.
+                    array with elements [0..N-1, 0..N-1].
+                    Exception is thrown if array size is too small.
+        N       -   size of matrix A, N>0
+        IsUpper -   if IsUpper=True, then A contains  upper  Cholesky  factor;
+                    otherwise A contains a lower one.
+        U       -   array[N], rank-1 update to A: A_mod = A + u*u'
+                    Exception is thrown if array size is too small.
+        BufR    -   possibly preallocated  buffer;  automatically  resized  if
+                    needed. It is recommended to  reuse  this  buffer  if  you
+                    perform a lot of subsequent decompositions.
+
+    OUTPUT PARAMETERS:
+        A       -   updated factorization.  If  IsUpper=True,  then  the  upper
+                    triangle contains matrix U, and the elements below the main
+                    diagonal are not modified. Similarly, if IsUpper = False.
+
+    NOTE: this function always succeeds, so it does not return completion code
+
+    NOTE: this function checks sizes of input arrays, but it does  NOT  checks
+          for presence of infinities or NAN's.
+
+      -- ALGLIB --
+         03.02.2014
+         Sergey Bochkanov
+    *************************************************************************/
+    public static void spdmatrixcholeskyupdateadd1(ref double[,] a, int n, bool isupper, double[] u)
+    {
+    
+        trfac.spdmatrixcholeskyupdateadd1(a, n, isupper, u, null);
+    }
+    
+    public static void spdmatrixcholeskyupdateadd1(ref double[,] a, int n, bool isupper, double[] u, alglib.xparams _params)
+    {
+    
+        trfac.spdmatrixcholeskyupdateadd1(a, n, isupper, u, _params);
+    }
+    
+    /*************************************************************************
+    Update of Cholesky decomposition: "fixing" some variables.
+
+    This function uses internally allocated buffer which is not saved  between
+    subsequent  calls.  So,  if  you  perform  a lot  of  subsequent  updates,
+    we  recommend   you   to   use   "buffered"   version   of  this function:
+    SPDMatrixCholeskyUpdateFixBuf().
+
+    "FIXING" EXPLAINED:
+
+        Suppose we have N*N positive definite matrix A. "Fixing" some variable
+        means filling corresponding row/column of  A  by  zeros,  and  setting
+        diagonal element to 1.
+
+        For example, if we fix 2nd variable in 4*4 matrix A, it becomes Af:
+
+            ( A00  A01  A02  A03 )      ( Af00  0   Af02 Af03 )
+            ( A10  A11  A12  A13 )      (  0    1    0    0   )
+            ( A20  A21  A22  A23 )  =>  ( Af20  0   Af22 Af23 )
+            ( A30  A31  A32  A33 )      ( Af30  0   Af32 Af33 )
+
+        If we have Cholesky decomposition of A, it must be recalculated  after
+        variables were  fixed.  However,  it  is  possible  to  use  efficient
+        algorithm, which needs O(K*N^2)  time  to  "fix"  K  variables,  given
+        Cholesky decomposition of original, "unfixed" A.
+
+    INPUT PARAMETERS:
+        A       -   upper or lower Cholesky factor.
+                    array with elements [0..N-1, 0..N-1].
+                    Exception is thrown if array size is too small.
+        N       -   size of matrix A, N>0
+        IsUpper -   if IsUpper=True, then A contains  upper  Cholesky  factor;
+                    otherwise A contains a lower one.
+        Fix     -   array[N], I-th element is True if I-th  variable  must  be
+                    fixed. Exception is thrown if array size is too small.
+        BufR    -   possibly preallocated  buffer;  automatically  resized  if
+                    needed. It is recommended to  reuse  this  buffer  if  you
+                    perform a lot of subsequent decompositions.
+
+    OUTPUT PARAMETERS:
+        A       -   updated factorization.  If  IsUpper=True,  then  the  upper
+                    triangle contains matrix U, and the elements below the main
+                    diagonal are not modified. Similarly, if IsUpper = False.
+
+    NOTE: this function always succeeds, so it does not return completion code
+
+    NOTE: this function checks sizes of input arrays, but it does  NOT  checks
+          for presence of infinities or NAN's.
+
+    NOTE: this  function  is  efficient  only  for  moderate amount of updated
+          variables - say, 0.1*N or 0.3*N. For larger amount of  variables  it
+          will  still  work,  but  you  may  get   better   performance   with
+          straightforward Cholesky.
+
+      -- ALGLIB --
+         03.02.2014
+         Sergey Bochkanov
+    *************************************************************************/
+    public static void spdmatrixcholeskyupdatefix(ref double[,] a, int n, bool isupper, bool[] fix)
+    {
+    
+        trfac.spdmatrixcholeskyupdatefix(a, n, isupper, fix, null);
+    }
+    
+    public static void spdmatrixcholeskyupdatefix(ref double[,] a, int n, bool isupper, bool[] fix, alglib.xparams _params)
+    {
+    
+        trfac.spdmatrixcholeskyupdatefix(a, n, isupper, fix, _params);
+    }
+    
+    /*************************************************************************
+    Update of Cholesky decomposition: rank-1 update to original A.  "Buffered"
+    version which uses preallocated buffer which is saved  between  subsequent
+    function calls.
+
+    See comments for SPDMatrixCholeskyUpdateAdd1() for more information.
+
+    INPUT PARAMETERS:
+        A       -   upper or lower Cholesky factor.
+                    array with elements [0..N-1, 0..N-1].
+                    Exception is thrown if array size is too small.
+        N       -   size of matrix A, N>0
+        IsUpper -   if IsUpper=True, then A contains  upper  Cholesky  factor;
+                    otherwise A contains a lower one.
+        U       -   array[N], rank-1 update to A: A_mod = A + u*u'
+                    Exception is thrown if array size is too small.
+        BufR    -   possibly preallocated  buffer;  automatically  resized  if
+                    needed. It is recommended to  reuse  this  buffer  if  you
+                    perform a lot of subsequent decompositions.
+
+    OUTPUT PARAMETERS:
+        A       -   updated factorization.  If  IsUpper=True,  then  the  upper
+                    triangle contains matrix U, and the elements below the main
+                    diagonal are not modified. Similarly, if IsUpper = False.
+
+      -- ALGLIB --
+         03.02.2014
+         Sergey Bochkanov
+    *************************************************************************/
+    public static void spdmatrixcholeskyupdateadd1buf(ref double[,] a, int n, bool isupper, double[] u, ref double[] bufr)
+    {
+    
+        trfac.spdmatrixcholeskyupdateadd1buf(a, n, isupper, u, ref bufr, null);
+    }
+    
+    public static void spdmatrixcholeskyupdateadd1buf(ref double[,] a, int n, bool isupper, double[] u, ref double[] bufr, alglib.xparams _params)
+    {
+    
+        trfac.spdmatrixcholeskyupdateadd1buf(a, n, isupper, u, ref bufr, _params);
+    }
+    
+    /*************************************************************************
+    Update of Cholesky  decomposition:  "fixing"  some  variables.  "Buffered"
+    version which uses preallocated buffer which is saved  between  subsequent
+    function calls.
+
+    See comments for SPDMatrixCholeskyUpdateFix() for more information.
+
+    INPUT PARAMETERS:
+        A       -   upper or lower Cholesky factor.
+                    array with elements [0..N-1, 0..N-1].
+                    Exception is thrown if array size is too small.
+        N       -   size of matrix A, N>0
+        IsUpper -   if IsUpper=True, then A contains  upper  Cholesky  factor;
+                    otherwise A contains a lower one.
+        Fix     -   array[N], I-th element is True if I-th  variable  must  be
+                    fixed. Exception is thrown if array size is too small.
+        BufR    -   possibly preallocated  buffer;  automatically  resized  if
+                    needed. It is recommended to  reuse  this  buffer  if  you
+                    perform a lot of subsequent decompositions.
+
+    OUTPUT PARAMETERS:
+        A       -   updated factorization.  If  IsUpper=True,  then  the  upper
+                    triangle contains matrix U, and the elements below the main
+                    diagonal are not modified. Similarly, if IsUpper = False.
+
+      -- ALGLIB --
+         03.02.2014
+         Sergey Bochkanov
+    *************************************************************************/
+    public static void spdmatrixcholeskyupdatefixbuf(ref double[,] a, int n, bool isupper, bool[] fix, ref double[] bufr)
+    {
+    
+        trfac.spdmatrixcholeskyupdatefixbuf(a, n, isupper, fix, ref bufr, null);
+    }
+    
+    public static void spdmatrixcholeskyupdatefixbuf(ref double[,] a, int n, bool isupper, bool[] fix, ref double[] bufr, alglib.xparams _params)
+    {
+    
+        trfac.spdmatrixcholeskyupdatefixbuf(a, n, isupper, fix, ref bufr, _params);
+    }
+    
+    /*************************************************************************
+    Sparse LU decomposition with column pivoting for sparsity and row pivoting
+    for stability. Input must be square sparse matrix stored in CRS format.
+
+    The algorithm  computes  LU  decomposition  of  a  general  square  matrix
+    (rectangular ones are not supported). The result  of  an  algorithm  is  a
+    representation of A as A = P*L*U*Q, where:
+    * L is lower unitriangular matrix
+    * U is upper triangular matrix
+    * P = P0*P1*...*PK, K=N-1, Pi - permutation matrix for I and P[I]
+    * Q = QK*...*Q1*Q0, K=N-1, Qi - permutation matrix for I and Q[I]
+
+    This function pivots columns for higher sparsity, and then pivots rows for
+    stability (larger element at the diagonal).
+
+    INPUT PARAMETERS:
+        A       -   sparse NxN matrix in CRS format. An exception is generated
+                    if matrix is non-CRS or non-square.
+        PivotType-  pivoting strategy:
+                    * 0 for best pivoting available (2 in current version)
+                    * 1 for row-only pivoting (NOT RECOMMENDED)
+                    * 2 for complete pivoting which produces most sparse outputs
+
+    OUTPUT PARAMETERS:
+        A       -   the result of factorization, matrices L and U stored in
+                    compact form using CRS sparse storage format:
+                    * lower unitriangular L is stored strictly under main diagonal
+                    * upper triangilar U is stored ON and ABOVE main diagonal
+        P       -   row permutation matrix in compact form, array[N]
+        Q       -   col permutation matrix in compact form, array[N]
+
+    This function always succeeds, i.e. it ALWAYS returns valid factorization,
+    but for your convenience it also returns  boolean  value  which  helps  to
+    detect symbolically degenerate matrices:
+    * function returns TRUE, if the matrix was factorized AND symbolically
+      non-degenerate
+    * function returns FALSE, if the matrix was factorized but U has strictly
+      zero elements at the diagonal (the factorization is returned anyway).
+
+
+      -- ALGLIB routine --
+         03.09.2018
+         Bochkanov Sergey
+    *************************************************************************/
+    public static bool sparselu(sparsematrix a, int pivottype, out int[] p, out int[] q)
+    {
+        p = new int[0];
+        q = new int[0];
+        return trfac.sparselu(a.innerobj, pivottype, ref p, ref q, null);
+    }
+    
+    public static bool sparselu(sparsematrix a, int pivottype, out int[] p, out int[] q, alglib.xparams _params)
+    {
+        p = new int[0];
+        q = new int[0];
+        return trfac.sparselu(a.innerobj, pivottype, ref p, ref q, _params);
+    }
+    
+    /*************************************************************************
+    Sparse Cholesky decomposition for skyline matrixm using in-place algorithm
+    without allocating additional storage.
+
+    The algorithm computes Cholesky decomposition  of  a  symmetric  positive-
+    definite sparse matrix. The result of an algorithm is a representation  of
+    A as A=U^T*U or A=L*L^T
+
+    This  function  is  a  more  efficient alternative to general, but  slower
+    SparseCholeskyX(), because it does not  create  temporary  copies  of  the
+    target. It performs factorization in-place, which gives  best  performance
+    on low-profile matrices. Its drawback, however, is that it can not perform
+    profile-reducing permutation of input matrix.
+
+    INPUT PARAMETERS:
+        A       -   sparse matrix in skyline storage (SKS) format.
+        N       -   size of matrix A (can be smaller than actual size of A)
+        IsUpper -   if IsUpper=True, then factorization is performed on  upper
+                    triangle. Another triangle is ignored (it may contant some
+                    data, but it is not changed).
+
+
+    OUTPUT PARAMETERS:
+        A       -   the result of factorization, stored in SKS. If IsUpper=True,
+                    then the upper  triangle  contains  matrix  U,  such  that
+                    A = U^T*U. Lower triangle is not changed.
+                    Similarly, if IsUpper = False. In this case L is returned,
+                    and we have A = L*(L^T).
+                    Note that THIS function does not  perform  permutation  of
+                    rows to reduce bandwidth.
+
+    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.
+
+    NOTE: for  performance  reasons  this  function  does NOT check that input
+          matrix  includes  only  finite  values. It is your responsibility to
+          make sure that there are no infinite or NAN values in the matrix.
+
+      -- ALGLIB routine --
+         16.01.2014
+         Bochkanov Sergey
+    *************************************************************************/
+    public static bool sparsecholeskyskyline(sparsematrix a, int n, bool isupper)
+    {
+    
+        return trfac.sparsecholeskyskyline(a.innerobj, n, isupper, null);
+    }
+    
+    public static bool sparsecholeskyskyline(sparsematrix a, int n, bool isupper, alglib.xparams _params)
+    {
+    
+        return trfac.sparsecholeskyskyline(a.innerobj, n, isupper, _params);
+    }
+
+}
+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)
+    {
+    
+        return rcond.rmatrixrcond1(a, n, null);
+    }
+    
+    public static double rmatrixrcond1(double[,] a, int n, alglib.xparams _params)
+    {
+    
+        return rcond.rmatrixrcond1(a, n, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.rmatrixrcondinf(a, n, null);
+    }
+    
+    public static double rmatrixrcondinf(double[,] a, int n, alglib.xparams _params)
+    {
+    
+        return rcond.rmatrixrcondinf(a, n, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.spdmatrixrcond(a, n, isupper, null);
+    }
+    
+    public static double spdmatrixrcond(double[,] a, int n, bool isupper, alglib.xparams _params)
+    {
+    
+        return rcond.spdmatrixrcond(a, n, isupper, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.rmatrixtrrcond1(a, n, isupper, isunit, null);
+    }
+    
+    public static double rmatrixtrrcond1(double[,] a, int n, bool isupper, bool isunit, alglib.xparams _params)
+    {
+    
+        return rcond.rmatrixtrrcond1(a, n, isupper, isunit, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.rmatrixtrrcondinf(a, n, isupper, isunit, null);
+    }
+    
+    public static double rmatrixtrrcondinf(double[,] a, int n, bool isupper, bool isunit, alglib.xparams _params)
+    {
+    
+        return rcond.rmatrixtrrcondinf(a, n, isupper, isunit, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.hpdmatrixrcond(a, n, isupper, null);
+    }
+    
+    public static double hpdmatrixrcond(complex[,] a, int n, bool isupper, alglib.xparams _params)
+    {
+    
+        return rcond.hpdmatrixrcond(a, n, isupper, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.cmatrixrcond1(a, n, null);
+    }
+    
+    public static double cmatrixrcond1(complex[,] a, int n, alglib.xparams _params)
+    {
+    
+        return rcond.cmatrixrcond1(a, n, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.cmatrixrcondinf(a, n, null);
+    }
+    
+    public static double cmatrixrcondinf(complex[,] a, int n, alglib.xparams _params)
+    {
+    
+        return rcond.cmatrixrcondinf(a, n, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.rmatrixlurcond1(lua, n, null);
+    }
+    
+    public static double rmatrixlurcond1(double[,] lua, int n, alglib.xparams _params)
+    {
+    
+        return rcond.rmatrixlurcond1(lua, n, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.rmatrixlurcondinf(lua, n, null);
+    }
+    
+    public static double rmatrixlurcondinf(double[,] lua, int n, alglib.xparams _params)
+    {
+    
+        return rcond.rmatrixlurcondinf(lua, n, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.spdmatrixcholeskyrcond(a, n, isupper, null);
+    }
+    
+    public static double spdmatrixcholeskyrcond(double[,] a, int n, bool isupper, alglib.xparams _params)
+    {
+    
+        return rcond.spdmatrixcholeskyrcond(a, n, isupper, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.hpdmatrixcholeskyrcond(a, n, isupper, null);
+    }
+    
+    public static double hpdmatrixcholeskyrcond(complex[,] a, int n, bool isupper, alglib.xparams _params)
+    {
+    
+        return rcond.hpdmatrixcholeskyrcond(a, n, isupper, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.cmatrixlurcond1(lua, n, null);
+    }
+    
+    public static double cmatrixlurcond1(complex[,] lua, int n, alglib.xparams _params)
+    {
+    
+        return rcond.cmatrixlurcond1(lua, n, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.cmatrixlurcondinf(lua, n, null);
+    }
+    
+    public static double cmatrixlurcondinf(complex[,] lua, int n, alglib.xparams _params)
+    {
+    
+        return rcond.cmatrixlurcondinf(lua, n, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.cmatrixtrrcond1(a, n, isupper, isunit, null);
+    }
+    
+    public static double cmatrixtrrcond1(complex[,] a, int n, bool isupper, bool isunit, alglib.xparams _params)
+    {
+    
+        return rcond.cmatrixtrrcond1(a, n, isupper, isunit, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return rcond.cmatrixtrrcondinf(a, n, isupper, isunit, null);
+    }
+    
+    public static double cmatrixtrrcondinf(complex[,] a, int n, bool isupper, bool isunit, alglib.xparams _params)
+    {
+    
+        return rcond.cmatrixtrrcondinf(a, n, isupper, isunit, _params);
+    }
+
+}
+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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new matinvreport((matinv.matinvreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixluinverse(ref double[,] a, int[] pivots, int n, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.rmatrixluinverse(ref a, pivots, n, ref info, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void rmatrixluinverse(ref double[,] a, int[] pivots, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Inversion of a general matrix.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixinverse(ref double[,] a, int n, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.rmatrixinverse(ref a, n, ref info, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void rmatrixinverse(ref double[,] a, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Inversion of a matrix given by its LU decomposition.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void cmatrixluinverse(ref complex[,] a, int[] pivots, int n, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.cmatrixluinverse(ref a, pivots, n, ref info, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void cmatrixluinverse(ref complex[,] a, int[] pivots, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Inversion of a general matrix.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void cmatrixinverse(ref complex[,] a, int n, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.cmatrixinverse(ref a, n, ref info, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void cmatrixinverse(ref complex[,] a, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Inversion of a symmetric positive definite matrix which is given
+    by Cholesky decomposition.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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 isn't  used/changed  by
+                      function
+                    * if False,  symmetric matrix  A  is  given  by  its lower
+                      triangle, and the  upper triangle isn't 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, null);
+    }
+    
+    public static void spdmatrixcholeskyinverse(ref double[,] a, int n, bool isupper, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.spdmatrixcholeskyinverse(ref a, n, isupper, ref info, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spdmatrixcholeskyinverse(ref double[,] a, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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 isn't  used/changed  by
+                      function
+                    * if False,  symmetric matrix  A  is  given  by  its lower
+                      triangle, and the  upper triangle isn't 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, null);
+    }
+    
+    public static void spdmatrixinverse(ref double[,] a, int n, bool isupper, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.spdmatrixinverse(ref a, n, isupper, ref info, rep.innerobj, _params);
+    }
+            
+    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, null);
+        if( !alglib.ap.forcesymmetric(a) )
+            throw new alglibexception("Internal error while forcing symmetricity of 'a' parameter");
+        return;
+    }
+            
+    public static void spdmatrixinverse(ref double[,] a, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        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, _params);
+        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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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 isn't  used/changed  by
+                      function
+                    * if False,  symmetric matrix  A  is  given  by  its lower
+                      triangle, and the  upper triangle isn't 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, null);
+    }
+    
+    public static void hpdmatrixcholeskyinverse(ref complex[,] a, int n, bool isupper, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.hpdmatrixcholeskyinverse(ref a, n, isupper, ref info, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void hpdmatrixcholeskyinverse(ref complex[,] a, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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 isn't  used/changed  by
+                      function
+                    * if False,  symmetric matrix  A  is  given  by  its lower
+                      triangle, and the  upper triangle isn't 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, null);
+    }
+    
+    public static void hpdmatrixinverse(ref complex[,] a, int n, bool isupper, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.hpdmatrixinverse(ref a, n, isupper, ref info, rep.innerobj, _params);
+    }
+            
+    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, null);
+        if( !alglib.ap.forcehermitian(a) )
+            throw new alglibexception("Internal error while forcing Hermitian properties of 'a' parameter");
+        return;
+    }
+            
+    public static void hpdmatrixinverse(ref complex[,] a, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        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, _params);
+        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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixtrinverse(ref double[,] a, int n, bool isupper, bool isunit, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.rmatrixtrinverse(ref a, n, isupper, isunit, ref info, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void rmatrixtrinverse(ref double[,] a, bool isupper, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void cmatrixtrinverse(ref complex[,] a, int n, bool isupper, bool isunit, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new matinvreport();
+        matinv.cmatrixtrinverse(ref a, n, isupper, isunit, ref info, rep.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void cmatrixtrinverse(ref complex[,] a, bool isupper, out int info, out matinvreport rep, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return;
+    }
+
+}
+public partial class alglib
+{
+
+    
+    /*************************************************************************
+    QR decomposition of a rectangular matrix of size MxN
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixqr(ref double[,] a, int m, int n, out double[] tau, alglib.xparams _params)
+    {
+        tau = new double[0];
+        ortfac.rmatrixqr(ref a, m, n, ref tau, _params);
+    }
+    
+    /*************************************************************************
+    LQ decomposition of a rectangular matrix of size MxN
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixlq(ref double[,] a, int m, int n, out double[] tau, alglib.xparams _params)
+    {
+        tau = new double[0];
+        ortfac.rmatrixlq(ref a, m, n, ref tau, _params);
+    }
+    
+    /*************************************************************************
+    QR decomposition of a rectangular complex matrix of size MxN
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void cmatrixqr(ref complex[,] a, int m, int n, out complex[] tau, alglib.xparams _params)
+    {
+        tau = new complex[0];
+        ortfac.cmatrixqr(ref a, m, n, ref tau, _params);
+    }
+    
+    /*************************************************************************
+    LQ decomposition of a rectangular complex matrix of size MxN
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void cmatrixlq(ref complex[,] a, int m, int n, out complex[] tau, alglib.xparams _params)
+    {
+        tau = new complex[0];
+        ortfac.cmatrixlq(ref a, m, n, ref tau, _params);
+    }
+    
+    /*************************************************************************
+    Partial unpacking of matrix Q from the QR decomposition of a matrix A
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixqrunpackq(double[,] a, int m, int n, double[] tau, int qcolumns, out double[,] q, alglib.xparams _params)
+    {
+        q = new double[0,0];
+        ortfac.rmatrixqrunpackq(a, m, n, tau, qcolumns, ref q, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rmatrixqrunpackr(double[,] a, int m, int n, out double[,] r, alglib.xparams _params)
+    {
+        r = new double[0,0];
+        ortfac.rmatrixqrunpackr(a, m, n, ref r, _params);
+    }
+    
+    /*************************************************************************
+    Partial unpacking of matrix Q from the LQ decomposition of a matrix A
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixlqunpackq(double[,] a, int m, int n, double[] tau, int qrows, out double[,] q, alglib.xparams _params)
+    {
+        q = new double[0,0];
+        ortfac.rmatrixlqunpackq(a, m, n, tau, qrows, ref q, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rmatrixlqunpackl(double[,] a, int m, int n, out double[,] l, alglib.xparams _params)
+    {
+        l = new double[0,0];
+        ortfac.rmatrixlqunpackl(a, m, n, ref l, _params);
+    }
+    
+    /*************************************************************************
+    Partial unpacking of matrix Q from QR decomposition of a complex matrix A.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void cmatrixqrunpackq(complex[,] a, int m, int n, complex[] tau, int qcolumns, out complex[,] q, alglib.xparams _params)
+    {
+        q = new complex[0,0];
+        ortfac.cmatrixqrunpackq(a, m, n, tau, qcolumns, ref q, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void cmatrixqrunpackr(complex[,] a, int m, int n, out complex[,] r, alglib.xparams _params)
+    {
+        r = new complex[0,0];
+        ortfac.cmatrixqrunpackr(a, m, n, ref r, _params);
+    }
+    
+    /*************************************************************************
+    Partial unpacking of matrix Q from LQ decomposition of a complex matrix A.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void cmatrixlqunpackq(complex[,] a, int m, int n, complex[] tau, int qrows, out complex[,] q, alglib.xparams _params)
+    {
+        q = new complex[0,0];
+        ortfac.cmatrixlqunpackq(a, m, n, tau, qrows, ref q, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void cmatrixlqunpackl(complex[,] a, int m, int n, out complex[,] l, alglib.xparams _params)
+    {
+        l = new complex[0,0];
+        ortfac.cmatrixlqunpackl(a, m, n, ref l, _params);
+    }
+    
+    /*************************************************************************
+    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^T).
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixbd(ref double[,] a, int m, int n, out double[] tauq, out double[] taup, alglib.xparams _params)
+    {
+        tauq = new double[0];
+        taup = new double[0];
+        ortfac.rmatrixbd(ref a, m, n, ref tauq, ref taup, _params);
+    }
+    
+    /*************************************************************************
+    Unpacking matrix Q which reduces a matrix to bidiagonal form.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixbdunpackq(double[,] qp, int m, int n, double[] tauq, int qcolumns, out double[,] q, alglib.xparams _params)
+    {
+        q = new double[0,0];
+        ortfac.rmatrixbdunpackq(qp, m, n, tauq, qcolumns, ref q, _params);
+    }
+    
+    /*************************************************************************
+    Multiplication by matrix Q which reduces matrix A to  bidiagonal form.
+
+    The algorithm allows pre- or post-multiply by Q or Q'.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixbdmultiplybyq(double[,] qp, int m, int n, double[] tauq, ref double[,] z, int zrows, int zcolumns, bool fromtheright, bool dotranspose, alglib.xparams _params)
+    {
+    
+        ortfac.rmatrixbdmultiplybyq(qp, m, n, tauq, ref z, zrows, zcolumns, fromtheright, dotranspose, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rmatrixbdunpackpt(double[,] qp, int m, int n, double[] taup, int ptrows, out double[,] pt, alglib.xparams _params)
+    {
+        pt = new double[0,0];
+        ortfac.rmatrixbdunpackpt(qp, m, n, taup, ptrows, ref pt, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rmatrixbdmultiplybyp(double[,] qp, int m, int n, double[] taup, ref double[,] z, int zrows, int zcolumns, bool fromtheright, bool dotranspose, alglib.xparams _params)
+    {
+    
+        ortfac.rmatrixbdmultiplybyp(qp, m, n, taup, ref z, zrows, zcolumns, fromtheright, dotranspose, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rmatrixbdunpackdiagonals(double[,] b, int m, int n, out bool isupper, out double[] d, out double[] e, alglib.xparams _params)
+    {
+        isupper = false;
+        d = new double[0];
+        e = new double[0];
+        ortfac.rmatrixbdunpackdiagonals(b, m, n, ref isupper, ref d, ref e, _params);
+    }
+    
+    /*************************************************************************
+    Reduction of a square matrix to  upper Hessenberg form: Q'*A*Q = H,
+    where Q is an orthogonal matrix, H - Hessenberg matrix.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixhessenberg(ref double[,] a, int n, out double[] tau, alglib.xparams _params)
+    {
+        tau = new double[0];
+        ortfac.rmatrixhessenberg(ref a, n, ref tau, _params);
+    }
+    
+    /*************************************************************************
+    Unpacking matrix Q which reduces matrix A to upper Hessenberg form
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void rmatrixhessenbergunpackq(double[,] a, int n, double[] tau, out double[,] q, alglib.xparams _params)
+    {
+        q = new double[0,0];
+        ortfac.rmatrixhessenbergunpackq(a, n, tau, ref q, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rmatrixhessenbergunpackh(double[,] a, int n, out double[,] h, alglib.xparams _params)
+    {
+        h = new double[0,0];
+        ortfac.rmatrixhessenbergunpackh(a, n, ref h, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void smatrixtd(ref double[,] a, int n, bool isupper, out double[] tau, out double[] d, out double[] e, alglib.xparams _params)
+    {
+        tau = new double[0];
+        d = new double[0];
+        e = new double[0];
+        ortfac.smatrixtd(ref a, n, isupper, ref tau, ref d, ref e, _params);
+    }
+    
+    /*************************************************************************
+    Unpacking matrix Q which reduces symmetric matrix to a tridiagonal
+    form.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void smatrixtdunpackq(double[,] a, int n, bool isupper, double[] tau, out double[,] q, alglib.xparams _params)
+    {
+        q = new double[0,0];
+        ortfac.smatrixtdunpackq(a, n, isupper, tau, ref q, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void hmatrixtd(ref complex[,] a, int n, bool isupper, out complex[] tau, out double[] d, out double[] e, alglib.xparams _params)
+    {
+        tau = new complex[0];
+        d = new double[0];
+        e = new double[0];
+        ortfac.hmatrixtd(ref a, n, isupper, ref tau, ref d, ref e, _params);
+    }
+    
+    /*************************************************************************
+    Unpacking matrix Q which reduces a Hermitian matrix to a real  tridiagonal
+    form.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void hmatrixtdunpackq(complex[,] a, int n, bool isupper, complex[] tau, out complex[,] q, alglib.xparams _params)
+    {
+        q = new complex[0,0];
+        ortfac.hmatrixtdunpackq(a, n, isupper, tau, ref q, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+    
+    /*************************************************************************
+    Singular value decomposition of a bidiagonal matrix (extended algorithm)
+
+    COMMERCIAL EDITION OF ALGLIB:
+
+      ! Commercial version of ALGLIB includes one  important  improvement   of
+      ! this function, which can be used from C++ and C#:
+      ! * Intel MKL support (lightweight Intel MKL is shipped with ALGLIB)
+      !
+      ! Intel MKL gives approximately constant  (with  respect  to  number  of
+      ! worker threads) acceleration factor which depends on CPU  being  used,
+      ! problem  size  and  "baseline"  ALGLIB  edition  which  is  used   for
+      ! comparison.
+      !
+      ! Generally, commercial ALGLIB is several times faster than  open-source
+      ! generic C edition, and many times faster than open-source C# edition.
+      !
+      ! Multithreaded acceleration is NOT supported for this function.
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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).
+
+    NOTE: multiplication U*Q is performed by means of transposition to internal
+          buffer, multiplication and backward transposition. It helps to avoid
+          costly columnwise operations and speed-up algorithm.
+
+    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)
+    {
+    
+        return bdsvd.rmatrixbdsvd(ref d, e, n, isupper, isfractionalaccuracyrequired, ref u, nru, ref c, ncc, ref vt, ncvt, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+    
+        return bdsvd.rmatrixbdsvd(ref d, e, n, isupper, isfractionalaccuracyrequired, ref u, nru, ref c, ncc, ref vt, ncvt, _params);
+    }
+
+}
+public partial class alglib
+{
+
+    
+    /*************************************************************************
+    Singular value decomposition of a rectangular matrix.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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 doesn't 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 isn't 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];
+        return svd.rmatrixsvd(a, m, n, uneeded, vtneeded, additionalmemory, ref w, ref u, ref vt, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        w = new double[0];
+        u = new double[0,0];
+        vt = new double[0,0];
+        return svd.rmatrixsvd(a, m, n, uneeded, vtneeded, additionalmemory, ref w, ref u, ref vt, _params);
+    }
+
+}
+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 : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public normestimatorstate()
+        {
+            _innerobj = new normestimator.normestimatorstate();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new normestimatorstate((normestimator.normestimatorstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void normestimatorcreate(int m, int n, int nstart, int nits, out normestimatorstate state, alglib.xparams _params)
+    {
+        state = new normestimatorstate();
+        normestimator.normestimatorcreate(m, n, nstart, nits, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void normestimatorsetseed(normestimatorstate state, int seedval, alglib.xparams _params)
+    {
+    
+        normestimator.normestimatorsetseed(state.innerobj, seedval, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void normestimatorestimatesparse(normestimatorstate state, sparsematrix a, alglib.xparams _params)
+    {
+    
+        normestimator.normestimatorestimatesparse(state.innerobj, a.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void normestimatorresults(normestimatorstate state, out double nrm, alglib.xparams _params)
+    {
+        nrm = 0;
+        normestimator.normestimatorresults(state.innerobj, ref nrm, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This object stores state of the subspace iteration algorithm.
+
+    You should use ALGLIB functions to work with this object.
+    *************************************************************************/
+    public class eigsubspacestate : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public eigsubspacestate()
+        {
+            _innerobj = new evd.eigsubspacestate();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new eigsubspacestate((evd.eigsubspacestate)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private evd.eigsubspacestate _innerobj;
+        public evd.eigsubspacestate innerobj { get { return _innerobj; } }
+        public eigsubspacestate(evd.eigsubspacestate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This object stores state of the subspace iteration algorithm.
+
+    You should use ALGLIB functions to work with this object.
+    *************************************************************************/
+    public class eigsubspacereport : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public int iterationscount { get { return _innerobj.iterationscount; } set { _innerobj.iterationscount = value; } }
+    
+        public eigsubspacereport()
+        {
+            _innerobj = new evd.eigsubspacereport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new eigsubspacereport((evd.eigsubspacereport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private evd.eigsubspacereport _innerobj;
+        public evd.eigsubspacereport innerobj { get { return _innerobj; } }
+        public eigsubspacereport(evd.eigsubspacereport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+    
+    /*************************************************************************
+    This function initializes subspace iteration solver. This solver  is  used
+    to solve symmetric real eigenproblems where just a few (top K) eigenvalues
+    and corresponding eigenvectors is required.
+
+    This solver can be significantly faster than  complete  EVD  decomposition
+    in the following case:
+    * when only just a small fraction  of  top  eigenpairs  of dense matrix is
+      required. When K approaches N, this solver is slower than complete dense
+      EVD
+    * when problem matrix is sparse (and/or is not known explicitly, i.e. only
+      matrix-matrix product can be performed)
+
+    USAGE (explicit dense/sparse matrix):
+    1. User initializes algorithm state with eigsubspacecreate() call
+    2. [optional] User tunes solver parameters by calling eigsubspacesetcond()
+       or other functions
+    3. User  calls  eigsubspacesolvedense() or eigsubspacesolvesparse() methods,
+       which take algorithm state and 2D array or alglib.sparsematrix object.
+
+    USAGE (out-of-core mode):
+    1. User initializes algorithm state with eigsubspacecreate() call
+    2. [optional] User tunes solver parameters by calling eigsubspacesetcond()
+       or other functions
+    3. User activates out-of-core mode of  the  solver  and  repeatedly  calls
+       communication functions in a loop like below:
+       > alglib.eigsubspaceoocstart(state)
+       > while alglib.eigsubspaceooccontinue(state) do
+       >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+       >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+       >     [calculate  Y=A*X, with X=R^NxM]
+       >     alglib.eigsubspaceoocsendresult(state, in Y)
+       > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+    INPUT PARAMETERS:
+        N       -   problem dimensionality, N>0
+        K       -   number of top eigenvector to calculate, 0<K<=N.
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+    NOTE: if you solve many similar EVD problems you may  find  it  useful  to
+          reuse previous subspace as warm-start point for new EVD problem.  It
+          can be done with eigsubspacesetwarmstart() function.
+
+      -- ALGLIB --
+         Copyright 16.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void eigsubspacecreate(int n, int k, out eigsubspacestate state)
+    {
+        state = new eigsubspacestate();
+        evd.eigsubspacecreate(n, k, state.innerobj, null);
+    }
+    
+    public static void eigsubspacecreate(int n, int k, out eigsubspacestate state, alglib.xparams _params)
+    {
+        state = new eigsubspacestate();
+        evd.eigsubspacecreate(n, k, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Buffered version of constructor which aims to reuse  previously  allocated
+    memory as much as possible.
+
+      -- ALGLIB --
+         Copyright 16.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void eigsubspacecreatebuf(int n, int k, eigsubspacestate state)
+    {
+    
+        evd.eigsubspacecreatebuf(n, k, state.innerobj, null);
+    }
+    
+    public static void eigsubspacecreatebuf(int n, int k, eigsubspacestate state, alglib.xparams _params)
+    {
+    
+        evd.eigsubspacecreatebuf(n, k, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function sets stopping critera for the solver:
+    * error in eigenvector/value allowed by solver
+    * maximum number of iterations to perform
+
+    INPUT PARAMETERS:
+        State       -   solver structure
+        Eps         -   eps>=0,  with non-zero value used to tell solver  that
+                        it can  stop  after  all  eigenvalues  converged  with
+                        error  roughly  proportional  to  eps*MAX(LAMBDA_MAX),
+                        where LAMBDA_MAX is a maximum eigenvalue.
+                        Zero  value  means  that  no  check  for  precision is
+                        performed.
+        MaxIts      -   maxits>=0,  with non-zero value used  to  tell  solver
+                        that it can stop after maxits  steps  (no  matter  how
+                        precise current estimate is)
+
+    NOTE: passing  eps=0  and  maxits=0  results  in  automatic  selection  of
+          moderate eps as stopping criteria (1.0E-6 in current implementation,
+          but it may change without notice).
+
+    NOTE: very small values of eps are possible (say, 1.0E-12),  although  the
+          larger problem you solve (N and/or K), the  harder  it  is  to  find
+          precise eigenvectors because rounding errors tend to accumulate.
+
+    NOTE: passing non-zero eps results in  some performance  penalty,  roughly
+          equal to 2N*(2K)^2 FLOPs per iteration. These additional computations
+          are required in order to estimate current error in  eigenvalues  via
+          Rayleigh-Ritz process.
+          Most of this additional time is  spent  in  construction  of  ~2Kx2K
+          symmetric  subproblem  whose  eigenvalues  are  checked  with  exact
+          eigensolver.
+          This additional time is negligible if you search for eigenvalues  of
+          the large dense matrix, but may become noticeable on  highly  sparse
+          EVD problems, where cost of matrix-matrix product is low.
+          If you set eps to exactly zero,  Rayleigh-Ritz  phase  is completely
+          turned off.
+
+      -- ALGLIB --
+         Copyright 16.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void eigsubspacesetcond(eigsubspacestate state, double eps, int maxits)
+    {
+    
+        evd.eigsubspacesetcond(state.innerobj, eps, maxits, null);
+    }
+    
+    public static void eigsubspacesetcond(eigsubspacestate state, double eps, int maxits, alglib.xparams _params)
+    {
+    
+        evd.eigsubspacesetcond(state.innerobj, eps, maxits, _params);
+    }
+    
+    /*************************************************************************
+    This function sets warm-start mode of the solver: next call to the  solver
+    will reuse previous subspace as warm-start  point.  It  can  significantly
+    speed-up convergence when you solve many similar eigenproblems.
+
+    INPUT PARAMETERS:
+        State       -   solver structure
+        UseWarmStart-   either True or False
+
+      -- ALGLIB --
+         Copyright 12.11.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void eigsubspacesetwarmstart(eigsubspacestate state, bool usewarmstart)
+    {
+    
+        evd.eigsubspacesetwarmstart(state.innerobj, usewarmstart, null);
+    }
+    
+    public static void eigsubspacesetwarmstart(eigsubspacestate state, bool usewarmstart, alglib.xparams _params)
+    {
+    
+        evd.eigsubspacesetwarmstart(state.innerobj, usewarmstart, _params);
+    }
+    
+    /*************************************************************************
+    This  function  initiates  out-of-core  mode  of  subspace eigensolver. It
+    should be used in conjunction with other out-of-core-related functions  of
+    this subspackage in a loop like below:
+
+    > alglib.eigsubspaceoocstart(state)
+    > while alglib.eigsubspaceooccontinue(state) do
+    >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+    >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+    >     [calculate  Y=A*X, with X=R^NxM]
+    >     alglib.eigsubspaceoocsendresult(state, in Y)
+    > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+    INPUT PARAMETERS:
+        State       -   solver object
+        MType       -   matrix type:
+                        * 0 for real  symmetric  matrix  (solver  assumes that
+                          matrix  being   processed  is  symmetric;  symmetric
+                          direct eigensolver is used for  smaller  subproblems
+                          arising during solution of larger "full" task)
+                        Future versions of ALGLIB may  introduce  support  for
+                        other  matrix   types;   for   now,   only   symmetric
+                        eigenproblems are supported.
+
+
+      -- ALGLIB --
+         Copyright 16.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void eigsubspaceoocstart(eigsubspacestate state, int mtype)
+    {
+    
+        evd.eigsubspaceoocstart(state.innerobj, mtype, null);
+    }
+    
+    public static void eigsubspaceoocstart(eigsubspacestate state, int mtype, alglib.xparams _params)
+    {
+    
+        evd.eigsubspaceoocstart(state.innerobj, mtype, _params);
+    }
+    
+    /*************************************************************************
+    This function performs subspace iteration  in  the  out-of-core  mode.  It
+    should be used in conjunction with other out-of-core-related functions  of
+    this subspackage in a loop like below:
+
+    > alglib.eigsubspaceoocstart(state)
+    > while alglib.eigsubspaceooccontinue(state) do
+    >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+    >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+    >     [calculate  Y=A*X, with X=R^NxM]
+    >     alglib.eigsubspaceoocsendresult(state, in Y)
+    > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+
+      -- ALGLIB --
+         Copyright 16.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static bool eigsubspaceooccontinue(eigsubspacestate state)
+    {
+    
+        return evd.eigsubspaceooccontinue(state.innerobj, null);
+    }
+    
+    public static bool eigsubspaceooccontinue(eigsubspacestate state, alglib.xparams _params)
+    {
+    
+        return evd.eigsubspaceooccontinue(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function is used to retrieve information  about  out-of-core  request
+    sent by solver to user code: request type (current version  of  the solver
+    sends only requests for matrix-matrix products) and request size (size  of
+    the matrices being multiplied).
+
+    This function returns just request metrics; in order  to  get contents  of
+    the matrices being multiplied, use eigsubspaceoocgetrequestdata().
+
+    It should be used in conjunction with other out-of-core-related  functions
+    of this subspackage in a loop like below:
+
+    > alglib.eigsubspaceoocstart(state)
+    > while alglib.eigsubspaceooccontinue(state) do
+    >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+    >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+    >     [calculate  Y=A*X, with X=R^NxM]
+    >     alglib.eigsubspaceoocsendresult(state, in Y)
+    > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+    INPUT PARAMETERS:
+        State           -   solver running in out-of-core mode
+
+    OUTPUT PARAMETERS:
+        RequestType     -   type of the request to process:
+                            * 0 - for matrix-matrix product A*X, with A  being
+                              NxN matrix whose eigenvalues/vectors are needed,
+                              and X being NxREQUESTSIZE one which is  returned
+                              by the eigsubspaceoocgetrequestdata().
+        RequestSize     -   size of the X matrix (number of columns),  usually
+                            it is several times larger than number of  vectors
+                            K requested by user.
+
+
+      -- ALGLIB --
+         Copyright 16.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void eigsubspaceoocgetrequestinfo(eigsubspacestate state, out int requesttype, out int requestsize)
+    {
+        requesttype = 0;
+        requestsize = 0;
+        evd.eigsubspaceoocgetrequestinfo(state.innerobj, ref requesttype, ref requestsize, null);
+    }
+    
+    public static void eigsubspaceoocgetrequestinfo(eigsubspacestate state, out int requesttype, out int requestsize, alglib.xparams _params)
+    {
+        requesttype = 0;
+        requestsize = 0;
+        evd.eigsubspaceoocgetrequestinfo(state.innerobj, ref requesttype, ref requestsize, _params);
+    }
+    
+    /*************************************************************************
+    This function is used to retrieve information  about  out-of-core  request
+    sent by solver to user code: matrix X (array[N,RequestSize) which have  to
+    be multiplied by out-of-core matrix A in a product A*X.
+
+    This function returns just request data; in order to get size of  the data
+    prior to processing requestm, use eigsubspaceoocgetrequestinfo().
+
+    It should be used in conjunction with other out-of-core-related  functions
+    of this subspackage in a loop like below:
+
+    > alglib.eigsubspaceoocstart(state)
+    > while alglib.eigsubspaceooccontinue(state) do
+    >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+    >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+    >     [calculate  Y=A*X, with X=R^NxM]
+    >     alglib.eigsubspaceoocsendresult(state, in Y)
+    > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+    INPUT PARAMETERS:
+        State           -   solver running in out-of-core mode
+        X               -   possibly  preallocated   storage;  reallocated  if
+                            needed, left unchanged, if large enough  to  store
+                            request data.
+
+    OUTPUT PARAMETERS:
+        X               -   array[N,RequestSize] or larger, leading  rectangle
+                            is filled with dense matrix X.
+
+
+      -- ALGLIB --
+         Copyright 16.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void eigsubspaceoocgetrequestdata(eigsubspacestate state, ref double[,] x)
+    {
+    
+        evd.eigsubspaceoocgetrequestdata(state.innerobj, ref x, null);
+    }
+    
+    public static void eigsubspaceoocgetrequestdata(eigsubspacestate state, ref double[,] x, alglib.xparams _params)
+    {
+    
+        evd.eigsubspaceoocgetrequestdata(state.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    This function is used to send user reply to out-of-core  request  sent  by
+    solver. Usually it is product A*X for returned by solver matrix X.
+
+    It should be used in conjunction with other out-of-core-related  functions
+    of this subspackage in a loop like below:
+
+    > alglib.eigsubspaceoocstart(state)
+    > while alglib.eigsubspaceooccontinue(state) do
+    >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+    >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+    >     [calculate  Y=A*X, with X=R^NxM]
+    >     alglib.eigsubspaceoocsendresult(state, in Y)
+    > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+    INPUT PARAMETERS:
+        State           -   solver running in out-of-core mode
+        AX              -   array[N,RequestSize] or larger, leading  rectangle
+                            is filled with product A*X.
+
+
+      -- ALGLIB --
+         Copyright 16.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void eigsubspaceoocsendresult(eigsubspacestate state, double[,] ax)
+    {
+    
+        evd.eigsubspaceoocsendresult(state.innerobj, ax, null);
+    }
+    
+    public static void eigsubspaceoocsendresult(eigsubspacestate state, double[,] ax, alglib.xparams _params)
+    {
+    
+        evd.eigsubspaceoocsendresult(state.innerobj, ax, _params);
+    }
+    
+    /*************************************************************************
+    This  function  finalizes out-of-core  mode  of  subspace eigensolver.  It
+    should be used in conjunction with other out-of-core-related functions  of
+    this subspackage in a loop like below:
+
+    > alglib.eigsubspaceoocstart(state)
+    > while alglib.eigsubspaceooccontinue(state) do
+    >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+    >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+    >     [calculate  Y=A*X, with X=R^NxM]
+    >     alglib.eigsubspaceoocsendresult(state, in Y)
+    > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+    INPUT PARAMETERS:
+        State       -   solver state
+
+    OUTPUT PARAMETERS:
+        W           -   array[K], depending on solver settings:
+                        * top  K  eigenvalues ordered  by  descending   -   if
+                          eigenvectors are returned in Z
+                        * zeros - if invariant subspace is returned in Z
+        Z           -   array[N,K], depending on solver settings either:
+                        * matrix of eigenvectors found
+                        * orthogonal basis of K-dimensional invariant subspace
+        Rep         -   report with additional parameters
+
+      -- ALGLIB --
+         Copyright 16.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void eigsubspaceoocstop(eigsubspacestate state, out double[] w, out double[,] z, out eigsubspacereport rep)
+    {
+        w = new double[0];
+        z = new double[0,0];
+        rep = new eigsubspacereport();
+        evd.eigsubspaceoocstop(state.innerobj, ref w, ref z, rep.innerobj, null);
+    }
+    
+    public static void eigsubspaceoocstop(eigsubspacestate state, out double[] w, out double[,] z, out eigsubspacereport rep, alglib.xparams _params)
+    {
+        w = new double[0];
+        z = new double[0,0];
+        rep = new eigsubspacereport();
+        evd.eigsubspaceoocstop(state.innerobj, ref w, ref z, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function runs eigensolver for dense NxN symmetric matrix A, given by
+    upper or lower triangle.
+
+    This function can not process nonsymmetric matrices.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        State       -   solver state
+        A           -   array[N,N], symmetric NxN matrix given by one  of  its
+                        triangles
+        IsUpper     -   whether upper or lower triangle of  A  is  given  (the
+                        other one is not referenced at all).
+
+    OUTPUT PARAMETERS:
+        W           -   array[K], top  K  eigenvalues ordered  by   descending
+                        of their absolute values
+        Z           -   array[N,K], matrix of eigenvectors found
+        Rep         -   report with additional parameters
+
+    NOTE: internally this function allocates a copy of NxN dense A. You should
+          take it into account when working with very large matrices occupying
+          almost all RAM.
+
+      -- ALGLIB --
+         Copyright 16.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void eigsubspacesolvedenses(eigsubspacestate state, double[,] a, bool isupper, out double[] w, out double[,] z, out eigsubspacereport rep)
+    {
+        w = new double[0];
+        z = new double[0,0];
+        rep = new eigsubspacereport();
+        evd.eigsubspacesolvedenses(state.innerobj, a, isupper, ref w, ref z, rep.innerobj, null);
+    }
+    
+    public static void eigsubspacesolvedenses(eigsubspacestate state, double[,] a, bool isupper, out double[] w, out double[,] z, out eigsubspacereport rep, alglib.xparams _params)
+    {
+        w = new double[0];
+        z = new double[0,0];
+        rep = new eigsubspacereport();
+        evd.eigsubspacesolvedenses(state.innerobj, a, isupper, ref w, ref z, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function runs eigensolver for dense NxN symmetric matrix A, given by
+    upper or lower triangle.
+
+    This function can not process nonsymmetric matrices.
+
+    INPUT PARAMETERS:
+        State       -   solver state
+        A           -   NxN symmetric matrix given by one of its triangles
+        IsUpper     -   whether upper or lower triangle of  A  is  given  (the
+                        other one is not referenced at all).
+
+    OUTPUT PARAMETERS:
+        W           -   array[K], top  K  eigenvalues ordered  by   descending
+                        of their absolute values
+        Z           -   array[N,K], matrix of eigenvectors found
+        Rep         -   report with additional parameters
+
+      -- ALGLIB --
+         Copyright 16.01.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void eigsubspacesolvesparses(eigsubspacestate state, sparsematrix a, bool isupper, out double[] w, out double[,] z, out eigsubspacereport rep)
+    {
+        w = new double[0];
+        z = new double[0,0];
+        rep = new eigsubspacereport();
+        evd.eigsubspacesolvesparses(state.innerobj, a.innerobj, isupper, ref w, ref z, rep.innerobj, null);
+    }
+    
+    public static void eigsubspacesolvesparses(eigsubspacestate state, sparsematrix a, bool isupper, out double[] w, out double[,] z, out eigsubspacereport rep, alglib.xparams _params)
+    {
+        w = new double[0];
+        z = new double[0,0];
+        rep = new eigsubspacereport();
+        evd.eigsubspacesolvesparses(state.innerobj, a.innerobj, isupper, ref w, ref z, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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 hasn't 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];
+        return evd.smatrixevd(a, n, zneeded, isupper, ref d, ref z, null);
+    }
+    
+    public static bool smatrixevd(double[,] a, int n, int zneeded, bool isupper, out double[] d, out double[,] z, alglib.xparams _params)
+    {
+        d = new double[0];
+        z = new double[0,0];
+        return evd.smatrixevd(a, n, zneeded, isupper, ref d, ref z, _params);
+    }
+    
+    /*************************************************************************
+    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
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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 hasn't 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];
+        return evd.smatrixevdr(a, n, zneeded, isupper, b1, b2, ref m, ref w, ref z, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        m = 0;
+        w = new double[0];
+        z = new double[0,0];
+        return evd.smatrixevdr(a, n, zneeded, isupper, b1, b2, ref m, ref w, ref z, _params);
+    }
+    
+    /*************************************************************************
+    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 hasn't 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];
+        return evd.smatrixevdi(a, n, zneeded, isupper, i1, i2, ref w, ref z, null);
+    }
+    
+    public static bool smatrixevdi(double[,] a, int n, int zneeded, bool isupper, int i1, int i2, out double[] w, out double[,] z, alglib.xparams _params)
+    {
+        w = new double[0];
+        z = new double[0,0];
+        return evd.smatrixevdi(a, n, zneeded, isupper, i1, i2, ref w, ref z, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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 hasn't 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];
+        return evd.hmatrixevd(a, n, zneeded, isupper, ref d, ref z, null);
+    }
+    
+    public static bool hmatrixevd(complex[,] a, int n, int zneeded, bool isupper, out double[] d, out complex[,] z, alglib.xparams _params)
+    {
+        d = new double[0];
+        z = new complex[0,0];
+        return evd.hmatrixevd(a, n, zneeded, isupper, ref d, ref z, _params);
+    }
+    
+    /*************************************************************************
+    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 hasn't 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];
+        return evd.hmatrixevdr(a, n, zneeded, isupper, b1, b2, ref m, ref w, ref z, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        m = 0;
+        w = new double[0];
+        z = new complex[0,0];
+        return evd.hmatrixevdr(a, n, zneeded, isupper, b1, b2, ref m, ref w, ref z, _params);
+    }
+    
+    /*************************************************************************
+    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 hasn't 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];
+        return evd.hmatrixevdi(a, n, zneeded, isupper, i1, i2, ref w, ref z, null);
+    }
+    
+    public static bool hmatrixevdi(complex[,] a, int n, int zneeded, bool isupper, int i1, int i2, out double[] w, out complex[,] z, alglib.xparams _params)
+    {
+        w = new double[0];
+        z = new complex[0,0];
+        return evd.hmatrixevdi(a, n, zneeded, isupper, i1, i2, ref w, ref z, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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 hasn't 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)
+    {
+    
+        return evd.smatrixtdevd(ref d, e, n, zneeded, ref z, null);
+    }
+    
+    public static bool smatrixtdevd(ref double[] d, double[] e, int n, int zneeded, ref double[,] z, alglib.xparams _params)
+    {
+    
+        return evd.smatrixtdevd(ref d, e, n, zneeded, ref z, _params);
+    }
+    
+    /*************************************************************************
+    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;
+        return evd.smatrixtdevdr(ref d, e, n, zneeded, a, b, ref m, ref z, null);
+    }
+    
+    public static bool smatrixtdevdr(ref double[] d, double[] e, int n, int zneeded, double a, double b, out int m, ref double[,] z, alglib.xparams _params)
+    {
+        m = 0;
+        return evd.smatrixtdevdr(ref d, e, n, zneeded, a, b, ref m, ref z, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return evd.smatrixtdevdi(ref d, e, n, zneeded, i1, i2, ref z, null);
+    }
+    
+    public static bool smatrixtdevdi(ref double[] d, double[] e, int n, int zneeded, int i1, int i2, ref double[,] z, alglib.xparams _params)
+    {
+    
+        return evd.smatrixtdevdi(ref d, e, n, zneeded, i1, i2, ref z, _params);
+    }
+    
+    /*************************************************************************
+    Finding eigenvalues and eigenvectors of a general (unsymmetric) matrix
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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];
+        return evd.rmatrixevd(a, n, vneeded, ref wr, ref wi, ref vl, ref vr, null);
+    }
+    
+    public static bool rmatrixevd(double[,] a, int n, int vneeded, out double[] wr, out double[] wi, out double[,] vl, out double[,] vr, alglib.xparams _params)
+    {
+        wr = new double[0];
+        wi = new double[0];
+        vl = new double[0,0];
+        vr = new double[0,0];
+        return evd.rmatrixevd(a, n, vneeded, ref wr, ref wi, ref vl, ref vr, _params);
+    }
+
+}
+public partial class alglib
+{
+
+    
+    /*************************************************************************
+    Subroutine performing the Schur decomposition of a general matrix by using
+    the QR algorithm with multiple shifts.
+
+    COMMERCIAL EDITION OF ALGLIB:
+
+      ! Commercial version of ALGLIB includes one  important  improvement   of
+      ! this function, which can be used from C++ and C#:
+      ! * Intel MKL support (lightweight Intel MKL is shipped with ALGLIB)
+      !
+      ! Intel MKL gives approximately constant  (with  respect  to  number  of
+      ! worker threads) acceleration factor which depends on CPU  being  used,
+      ! problem  size  and  "baseline"  ALGLIB  edition  which  is  used   for
+      ! comparison.
+      !
+      ! Multithreaded acceleration is NOT supported for this function.
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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];
+        return schur.rmatrixschur(ref a, n, ref s, null);
+    }
+    
+    public static bool rmatrixschur(ref double[,] a, int n, out double[,] s, alglib.xparams _params)
+    {
+        s = new double[0,0];
+        return schur.rmatrixschur(ref a, n, ref s, _params);
+    }
+
+}
+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 hasn't 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 isn't 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];
+        return spdgevd.smatrixgevd(a, n, isuppera, b, isupperb, zneeded, problemtype, ref d, ref z, null);
+    }
+    
+    public static bool smatrixgevd(double[,] a, int n, bool isuppera, double[,] b, bool isupperb, int zneeded, int problemtype, out double[] d, out double[,] z, alglib.xparams _params)
+    {
+        d = new double[0];
+        z = new double[0,0];
+        return spdgevd.smatrixgevd(a, n, isuppera, b, isupperb, zneeded, problemtype, ref d, ref z, _params);
+    }
+    
+    /*************************************************************************
+    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;
+        return spdgevd.smatrixgevdreduce(ref a, n, isuppera, b, isupperb, problemtype, ref r, ref isupperr, null);
+    }
+    
+    public static bool smatrixgevdreduce(ref double[,] a, int n, bool isuppera, double[,] b, bool isupperb, int problemtype, out double[,] r, out bool isupperr, alglib.xparams _params)
+    {
+        r = new double[0,0];
+        isupperr = false;
+        return spdgevd.smatrixgevdreduce(ref a, n, isuppera, b, isupperb, problemtype, ref r, ref isupperr, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void rmatrixinvupdatesimple(ref double[,] inva, int n, int updrow, int updcolumn, double updval, alglib.xparams _params)
+    {
+    
+        inverseupdate.rmatrixinvupdatesimple(ref inva, n, updrow, updcolumn, updval, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rmatrixinvupdaterow(ref double[,] inva, int n, int updrow, double[] v, alglib.xparams _params)
+    {
+    
+        inverseupdate.rmatrixinvupdaterow(ref inva, n, updrow, v, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rmatrixinvupdatecolumn(ref double[,] inva, int n, int updcolumn, double[] u, alglib.xparams _params)
+    {
+    
+        inverseupdate.rmatrixinvupdatecolumn(ref inva, n, updcolumn, u, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void rmatrixinvupdateuv(ref double[,] inva, int n, double[] u, double[] v, alglib.xparams _params)
+    {
+    
+        inverseupdate.rmatrixinvupdateuv(ref inva, n, u, v, _params);
+    }
+
+}
+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)
+    {
+    
+        return matdet.rmatrixludet(a, pivots, n, null);
+    }
+    
+    public static double rmatrixludet(double[,] a, int[] pivots, int n, alglib.xparams _params)
+    {
+    
+        return matdet.rmatrixludet(a, pivots, n, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static double rmatrixludet(double[,] a, int[] pivots, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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)
+    {
+    
+        return matdet.rmatrixdet(a, n, null);
+    }
+    
+    public static double rmatrixdet(double[,] a, int n, alglib.xparams _params)
+    {
+    
+        return matdet.rmatrixdet(a, n, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static double rmatrixdet(double[,] a, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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)
+    {
+    
+        return matdet.cmatrixludet(a, pivots, n, null);
+    }
+    
+    public static complex cmatrixludet(complex[,] a, int[] pivots, int n, alglib.xparams _params)
+    {
+    
+        return matdet.cmatrixludet(a, pivots, n, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static complex cmatrixludet(complex[,] a, int[] pivots, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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)
+    {
+    
+        return matdet.cmatrixdet(a, n, null);
+    }
+    
+    public static complex cmatrixdet(complex[,] a, int n, alglib.xparams _params)
+    {
+    
+        return matdet.cmatrixdet(a, n, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static complex cmatrixdet(complex[,] a, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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,
+    it's 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)
+    {
+    
+        return matdet.spdmatrixcholeskydet(a, n, null);
+    }
+    
+    public static double spdmatrixcholeskydet(double[,] a, int n, alglib.xparams _params)
+    {
+    
+        return matdet.spdmatrixcholeskydet(a, n, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static double spdmatrixcholeskydet(double[,] a, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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 isn't used/changed  by
+                      function
+                    * if False, symmetric matrix  A  is  given  by  its lower
+                      triangle, and the upper triangle isn't 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)
+    {
+    
+        return matdet.spdmatrixdet(a, n, isupper, null);
+    }
+    
+    public static double spdmatrixdet(double[,] a, int n, bool isupper, alglib.xparams _params)
+    {
+    
+        return matdet.spdmatrixdet(a, n, isupper, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static double spdmatrixdet(double[,] a, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return result;
+    }
+
+}
+public partial class alglib
+{
+    public class sparse
+    {
+        /*************************************************************************
+        Sparse matrix structure.
+
+        You should use ALGLIB functions to work with sparse matrix. Never  try  to
+        access its fields directly!
+
+        NOTES ON THE SPARSE STORAGE FORMATS
+
+        Sparse matrices can be stored using several formats:
+        * Hash-Table representation
+        * Compressed Row Storage (CRS)
+        * Skyline matrix storage (SKS)
+
+        Each of the formats has benefits and drawbacks:
+        * Hash-table is good for dynamic operations (insertion of new elements),
+          but does not support linear algebra operations
+        * CRS is good for operations like matrix-vector or matrix-matrix products,
+          but its initialization is less convenient - you have to tell row   sizes 
+          at the initialization, and you have to fill  matrix  only  row  by  row,
+          from left to right.
+        * SKS is a special format which is used to store triangular  factors  from
+          Cholesky factorization. It does not support  dynamic  modification,  and
+          support for linear algebra operations is very limited.
+
+        Tables below outline information about these two formats:
+
+            OPERATIONS WITH MATRIX      HASH        CRS         SKS
+            creation                    +           +           +
+            SparseGet                   +           +           +
+            SparseRewriteExisting       +           +           +
+            SparseSet                   +           +           +
+            SparseAdd                   +
+            SparseGetRow                            +           +
+            SparseGetCompressedRow                  +           +
+            sparse-dense linear algebra             +           +
+
+        *************************************************************************/
+        public class sparsematrix : apobject
+        {
+            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 int tablesize;
+            public sparsematrix()
+            {
+                init();
+            }
+            public override void init()
+            {
+                vals = new double[0];
+                idx = new int[0];
+                ridx = new int[0];
+                didx = new int[0];
+                uidx = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                sparsematrix _result = new sparsematrix();
+                _result.vals = (double[])vals.Clone();
+                _result.idx = (int[])idx.Clone();
+                _result.ridx = (int[])ridx.Clone();
+                _result.didx = (int[])didx.Clone();
+                _result.uidx = (int[])uidx.Clone();
+                _result.matrixtype = matrixtype;
+                _result.m = m;
+                _result.n = n;
+                _result.nfree = nfree;
+                _result.ninitialized = ninitialized;
+                _result.tablesize = tablesize;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Temporary buffers for sparse matrix operations.
+
+        You should pass an instance of this structure to factorization  functions.
+        It allows to reuse memory during repeated sparse  factorizations.  You  do
+        not have to call some initialization function - simply passing an instance
+        to factorization function is enough.
+
+        *************************************************************************/
+        public class sparsebuffers : apobject
+        {
+            public int[] d;
+            public int[] u;
+            public sparsematrix s;
+            public sparsebuffers()
+            {
+                init();
+            }
+            public override void init()
+            {
+                d = new int[0];
+                u = new int[0];
+                s = new sparsematrix();
+            }
+            public override alglib.apobject make_copy()
+            {
+                sparsebuffers _result = new sparsebuffers();
+                _result.d = (int[])d.Clone();
+                _result.u = (int[])u.Clone();
+                _result.s = (sparsematrix)s.make_copy();
+                return _result;
+            }
+        };
+
+
+
+
+        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
+
+        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.
+
+        NOTE 2
+
+        Comments of SparseMatrix structure outline  information  about  different
+        sparse storage formats. We recommend you to read them before starting  to
+        use ALGLIB sparse matrices.
+
+        NOTE 3
+
+        This function completely  overwrites S with new sparse matrix. Previously
+        allocated storage is NOT reused. If you  want  to reuse already allocated
+        memory, call SparseCreateBuf function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecreate(int m,
+            int n,
+            int k,
+            sparsematrix s,
+            alglib.xparams _params)
+        {
+            sparsecreatebuf(m, n, k, s, _params);
+        }
+
+
+        /*************************************************************************
+        This version of SparseCreate function creates sparse matrix in Hash-Table
+        format, reusing previously allocated storage as much  as  possible.  Read
+        comments for SparseCreate() for more information.
+
+        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.
+            S           -   SparseMatrix structure which MAY contain some  already
+                            allocated storage.
+
+        OUTPUT PARAMETERS
+            S           -   sparse M*N matrix in Hash-Table representation.
+                            All elements of the matrix are zero.
+                            Previously allocated storage is reused, if  its  size
+                            is compatible with expected number of non-zeros K.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.01.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecreatebuf(int m,
+            int n,
+            int k,
+            sparsematrix s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(m>0, "SparseCreateBuf: M<=0");
+            alglib.ap.assert(n>0, "SparseCreateBuf: N<=0");
+            alglib.ap.assert(k>=0, "SparseCreateBuf: K<0");
+            
+            //
+            // Hash-table size is max(existing_size,requested_size)
+            //
+            // NOTE: it is important to use ALL available memory for hash table
+            //       because it is impossible to efficiently reallocate table
+            //       without temporary storage. So, if we want table with up to
+            //       1.000.000 elements, we have to create such table from the
+            //       very beginning. Otherwise, the very idea of memory reuse
+            //       will be compromised.
+            //
+            s.tablesize = (int)Math.Round(k/desiredloadfactor+additional);
+            apserv.rvectorsetlengthatleast(ref s.vals, s.tablesize, _params);
+            s.tablesize = alglib.ap.len(s.vals);
+            
+            //
+            // Initialize other fields
+            //
+            s.matrixtype = 0;
+            s.m = m;
+            s.n = n;
+            s.nfree = s.tablesize;
+            apserv.ivectorsetlengthatleast(ref s.idx, 2*s.tablesize, _params);
+            for(i=0; i<=s.tablesize-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 
+        in comments on SparseMatrix structure.  We recommend  you  to  read  them
+        before starting to use ALGLIB sparse matrices..
+
+        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: this function completely  overwrites  S  with  new  sparse  matrix.
+              Previously allocated storage is NOT reused. If you  want  to  reuse
+              already allocated memory, call SparseCreateCRSBuf function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecreatecrs(int m,
+            int n,
+            int[] ner,
+            sparsematrix s,
+            alglib.xparams _params)
+        {
+            int i = 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");
+            for(i=0; i<=m-1; i++)
+            {
+                alglib.ap.assert(ner[i]>=0, "SparseCreateCRS: NER[] contains negative elements");
+            }
+            sparsecreatecrsbuf(m, n, ner, s, _params);
+        }
+
+
+        /*************************************************************************
+        This function creates sparse matrix in a CRS format (expert function  for
+        situations when you are running out  of  memory).  This  version  of  CRS
+        matrix creation function may reuse memory already allocated in S.
+
+        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 
+        in comments on SparseMatrix structure.  We recommend  you  to  read  them
+        before starting to use ALGLIB sparse matrices..
+
+        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
+            S           -   sparse matrix structure with possibly preallocated
+                            memory.
+
+        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.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecreatecrsbuf(int m,
+            int n,
+            int[] ner,
+            sparsematrix s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int noe = 0;
+
+            alglib.ap.assert(m>0, "SparseCreateCRSBuf: M<=0");
+            alglib.ap.assert(n>0, "SparseCreateCRSBuf: N<=0");
+            alglib.ap.assert(alglib.ap.len(ner)>=m, "SparseCreateCRSBuf: Length(NER)<M");
+            noe = 0;
+            s.matrixtype = 1;
+            s.ninitialized = 0;
+            s.m = m;
+            s.n = n;
+            apserv.ivectorsetlengthatleast(ref s.ridx, s.m+1, _params);
+            s.ridx[0] = 0;
+            for(i=0; i<=s.m-1; i++)
+            {
+                alglib.ap.assert(ner[i]>=0, "SparseCreateCRSBuf: NER[] contains negative elements");
+                noe = noe+ner[i];
+                s.ridx[i+1] = s.ridx[i]+ner[i];
+            }
+            apserv.rvectorsetlengthatleast(ref s.vals, noe, _params);
+            apserv.ivectorsetlengthatleast(ref s.idx, noe, _params);
+            if( noe==0 )
+            {
+                sparseinitduidx(s, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        This function creates sparse matrix in  a  SKS  format  (skyline  storage
+        format). In most cases you do not need this function - CRS format  better
+        suits most use cases.
+
+        INPUT PARAMETERS
+            M, N        -   number of rows(M) and columns (N) in a matrix:
+                            * M=N (as for now, ALGLIB supports only square SKS)
+                            * N>=1
+                            * M>=1
+            D           -   "bottom" bandwidths, array[M], D[I]>=0.
+                            I-th element stores number of non-zeros at I-th  row,
+                            below the diagonal (diagonal itself is not  included)
+            U           -   "top" bandwidths, array[N], U[I]>=0.
+                            I-th element stores number of non-zeros  at I-th row,
+                            above the diagonal (diagonal itself  is not included)
+
+        OUTPUT PARAMETERS
+            S           -   sparse M*N matrix in SKS representation.
+                            All elements are filled by zeros.
+                            You may use sparseset() to change their values.
+                            
+        NOTE: this function completely  overwrites  S  with  new  sparse  matrix.
+              Previously allocated storage is NOT reused. If you  want  to  reuse
+              already allocated memory, call SparseCreateSKSBuf function.
+
+          -- ALGLIB PROJECT --
+             Copyright 13.01.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecreatesks(int m,
+            int n,
+            int[] d,
+            int[] u,
+            sparsematrix s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(m>0, "SparseCreateSKS: M<=0");
+            alglib.ap.assert(n>0, "SparseCreateSKS: N<=0");
+            alglib.ap.assert(m==n, "SparseCreateSKS: M<>N");
+            alglib.ap.assert(alglib.ap.len(d)>=m, "SparseCreateSKS: Length(D)<M");
+            alglib.ap.assert(alglib.ap.len(u)>=n, "SparseCreateSKS: Length(U)<N");
+            for(i=0; i<=m-1; i++)
+            {
+                alglib.ap.assert(d[i]>=0, "SparseCreateSKS: D[] contains negative elements");
+                alglib.ap.assert(d[i]<=i, "SparseCreateSKS: D[I]>I for some I");
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(u[i]>=0, "SparseCreateSKS: U[] contains negative elements");
+                alglib.ap.assert(u[i]<=i, "SparseCreateSKS: U[I]>I for some I");
+            }
+            sparsecreatesksbuf(m, n, d, u, s, _params);
+        }
+
+
+        /*************************************************************************
+        This is "buffered"  version  of  SparseCreateSKS()  which  reuses  memory
+        previously allocated in S (of course, memory is reallocated if needed).
+
+        This function creates sparse matrix in  a  SKS  format  (skyline  storage
+        format). In most cases you do not need this function - CRS format  better
+        suits most use cases.
+
+        INPUT PARAMETERS
+            M, N        -   number of rows(M) and columns (N) in a matrix:
+                            * M=N (as for now, ALGLIB supports only square SKS)
+                            * N>=1
+                            * M>=1
+            D           -   "bottom" bandwidths, array[M], 0<=D[I]<=I.
+                            I-th element stores number of non-zeros at I-th row,
+                            below the diagonal (diagonal itself is not included)
+            U           -   "top" bandwidths, array[N], 0<=U[I]<=I.
+                            I-th element stores number of non-zeros at I-th row,
+                            above the diagonal (diagonal itself is not included)
+
+        OUTPUT PARAMETERS
+            S           -   sparse M*N matrix in SKS representation.
+                            All elements are filled by zeros.
+                            You may use sparseset() to change their values.
+
+          -- ALGLIB PROJECT --
+             Copyright 13.01.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecreatesksbuf(int m,
+            int n,
+            int[] d,
+            int[] u,
+            sparsematrix s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int minmn = 0;
+            int nz = 0;
+            int mxd = 0;
+            int mxu = 0;
+
+            alglib.ap.assert(m>0, "SparseCreateSKSBuf: M<=0");
+            alglib.ap.assert(n>0, "SparseCreateSKSBuf: N<=0");
+            alglib.ap.assert(m==n, "SparseCreateSKSBuf: M<>N");
+            alglib.ap.assert(alglib.ap.len(d)>=m, "SparseCreateSKSBuf: Length(D)<M");
+            alglib.ap.assert(alglib.ap.len(u)>=n, "SparseCreateSKSBuf: Length(U)<N");
+            for(i=0; i<=m-1; i++)
+            {
+                alglib.ap.assert(d[i]>=0, "SparseCreateSKSBuf: D[] contains negative elements");
+                alglib.ap.assert(d[i]<=i, "SparseCreateSKSBuf: D[I]>I for some I");
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(u[i]>=0, "SparseCreateSKSBuf: U[] contains negative elements");
+                alglib.ap.assert(u[i]<=i, "SparseCreateSKSBuf: U[I]>I for some I");
+            }
+            minmn = Math.Min(m, n);
+            s.matrixtype = 2;
+            s.ninitialized = 0;
+            s.m = m;
+            s.n = n;
+            apserv.ivectorsetlengthatleast(ref s.ridx, minmn+1, _params);
+            s.ridx[0] = 0;
+            nz = 0;
+            for(i=0; i<=minmn-1; i++)
+            {
+                nz = nz+1+d[i]+u[i];
+                s.ridx[i+1] = s.ridx[i]+1+d[i]+u[i];
+            }
+            apserv.rvectorsetlengthatleast(ref s.vals, nz, _params);
+            for(i=0; i<=nz-1; i++)
+            {
+                s.vals[i] = 0.0;
+            }
+            apserv.ivectorsetlengthatleast(ref s.didx, m+1, _params);
+            mxd = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                s.didx[i] = d[i];
+                mxd = Math.Max(mxd, d[i]);
+            }
+            s.didx[m] = mxd;
+            apserv.ivectorsetlengthatleast(ref s.uidx, n+1, _params);
+            mxu = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                s.uidx[i] = u[i];
+                mxu = Math.Max(mxu, u[i]);
+            }
+            s.uidx[n] = mxu;
+        }
+
+
+        /*************************************************************************
+        This function creates sparse matrix in  a  SKS  format  (skyline  storage
+        format). Unlike more general  sparsecreatesks(),  this  function  creates
+        sparse matrix with constant bandwidth.
+
+        You may want to use this function instead of sparsecreatesks() when  your
+        matrix has  constant  or  nearly-constant  bandwidth,  and  you  want  to
+        simplify source code.
+
+        INPUT PARAMETERS
+            M, N        -   number of rows(M) and columns (N) in a matrix:
+                            * M=N (as for now, ALGLIB supports only square SKS)
+                            * N>=1
+                            * M>=1
+            BW          -   matrix bandwidth, BW>=0
+
+        OUTPUT PARAMETERS
+            S           -   sparse M*N matrix in SKS representation.
+                            All elements are filled by zeros.
+                            You may use sparseset() to  change  their values.
+                            
+        NOTE: this function completely  overwrites  S  with  new  sparse  matrix.
+              Previously allocated storage is NOT reused. If you  want  to  reuse
+              already allocated memory, call sparsecreatesksbandbuf function.
+
+          -- ALGLIB PROJECT --
+             Copyright 25.12.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecreatesksband(int m,
+            int n,
+            int bw,
+            sparsematrix s,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(m>0, "SparseCreateSKSBand: M<=0");
+            alglib.ap.assert(n>0, "SparseCreateSKSBand: N<=0");
+            alglib.ap.assert(bw>=0, "SparseCreateSKSBand: BW<0");
+            alglib.ap.assert(m==n, "SparseCreateSKSBand: M!=N");
+            sparsecreatesksbandbuf(m, n, bw, s, _params);
+        }
+
+
+        /*************************************************************************
+        This is "buffered" version  of  sparsecreatesksband() which reuses memory
+        previously allocated in S (of course, memory is reallocated if needed).
+
+        You may want to use this function instead  of  sparsecreatesksbuf()  when
+        your matrix has  constant or nearly-constant  bandwidth,  and you want to
+        simplify source code.
+
+        INPUT PARAMETERS
+            M, N        -   number of rows(M) and columns (N) in a matrix:
+                            * M=N (as for now, ALGLIB supports only square SKS)
+                            * N>=1
+                            * M>=1
+            BW          -   bandwidth, BW>=0
+
+        OUTPUT PARAMETERS
+            S           -   sparse M*N matrix in SKS representation.
+                            All elements are filled by zeros.
+                            You may use sparseset() to change their values.
+
+          -- ALGLIB PROJECT --
+             Copyright 13.01.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecreatesksbandbuf(int m,
+            int n,
+            int bw,
+            sparsematrix s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int minmn = 0;
+            int nz = 0;
+            int mxd = 0;
+            int mxu = 0;
+            int dui = 0;
+
+            alglib.ap.assert(m>0, "SparseCreateSKSBandBuf: M<=0");
+            alglib.ap.assert(n>0, "SparseCreateSKSBandBuf: N<=0");
+            alglib.ap.assert(m==n, "SparseCreateSKSBandBuf: M!=N");
+            alglib.ap.assert(bw>=0, "SparseCreateSKSBandBuf: BW<0");
+            minmn = Math.Min(m, n);
+            s.matrixtype = 2;
+            s.ninitialized = 0;
+            s.m = m;
+            s.n = n;
+            apserv.ivectorsetlengthatleast(ref s.ridx, minmn+1, _params);
+            s.ridx[0] = 0;
+            nz = 0;
+            for(i=0; i<=minmn-1; i++)
+            {
+                dui = Math.Min(i, bw);
+                nz = nz+1+2*dui;
+                s.ridx[i+1] = s.ridx[i]+1+2*dui;
+            }
+            apserv.rvectorsetlengthatleast(ref s.vals, nz, _params);
+            for(i=0; i<=nz-1; i++)
+            {
+                s.vals[i] = 0.0;
+            }
+            apserv.ivectorsetlengthatleast(ref s.didx, m+1, _params);
+            mxd = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                dui = Math.Min(i, bw);
+                s.didx[i] = dui;
+                mxd = Math.Max(mxd, dui);
+            }
+            s.didx[m] = mxd;
+            apserv.ivectorsetlengthatleast(ref s.uidx, n+1, _params);
+            mxu = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                dui = Math.Min(i, bw);
+                s.uidx[i] = dui;
+                mxu = Math.Max(mxu, dui);
+            }
+            s.uidx[n] = mxu;
+        }
+
+
+        /*************************************************************************
+        This function copies S0 to S1.
+        This function completely deallocates memory owned by S1 before creating a
+        copy of S0. If you want to reuse memory, use SparseCopyBuf.
+
+        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,
+            alglib.xparams _params)
+        {
+            sparsecopybuf(s0, s1, _params);
+        }
+
+
+        /*************************************************************************
+        This function copies S0 to S1.
+        Memory already allocated in S1 is reused as much as possible.
+
+        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 sparsecopybuf(sparsematrix s0,
+            sparsematrix s1,
+            alglib.xparams _params)
+        {
+            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;
+            s1.tablesize = s0.tablesize;
+            
+            //
+            // Initialization for arrays
+            //
+            l = alglib.ap.len(s0.vals);
+            apserv.rvectorsetlengthatleast(ref s1.vals, l, _params);
+            for(i=0; i<=l-1; i++)
+            {
+                s1.vals[i] = s0.vals[i];
+            }
+            l = alglib.ap.len(s0.ridx);
+            apserv.ivectorsetlengthatleast(ref s1.ridx, l, _params);
+            for(i=0; i<=l-1; i++)
+            {
+                s1.ridx[i] = s0.ridx[i];
+            }
+            l = alglib.ap.len(s0.idx);
+            apserv.ivectorsetlengthatleast(ref s1.idx, l, _params);
+            for(i=0; i<=l-1; i++)
+            {
+                s1.idx[i] = s0.idx[i];
+            }
+            
+            //
+            // Initalization for CRS-parameters
+            //
+            l = alglib.ap.len(s0.uidx);
+            apserv.ivectorsetlengthatleast(ref s1.uidx, l, _params);
+            for(i=0; i<=l-1; i++)
+            {
+                s1.uidx[i] = s0.uidx[i];
+            }
+            l = alglib.ap.len(s0.didx);
+            apserv.ivectorsetlengthatleast(ref s1.didx, l, _params);
+            for(i=0; i<=l-1; i++)
+            {
+                s1.didx[i] = s0.didx[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function efficiently swaps contents of S0 and S1.
+
+          -- ALGLIB PROJECT --
+             Copyright 16.01.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparseswap(sparsematrix s0,
+            sparsematrix s1,
+            alglib.xparams _params)
+        {
+            apserv.swapi(ref s1.matrixtype, ref s0.matrixtype, _params);
+            apserv.swapi(ref s1.m, ref s0.m, _params);
+            apserv.swapi(ref s1.n, ref s0.n, _params);
+            apserv.swapi(ref s1.nfree, ref s0.nfree, _params);
+            apserv.swapi(ref s1.ninitialized, ref s0.ninitialized, _params);
+            apserv.swapi(ref s1.tablesize, ref s0.tablesize, _params);
+            alglib.ap.swap(ref s1.vals, ref s0.vals);
+            alglib.ap.swap(ref s1.ridx, ref s0.ridx);
+            alglib.ap.swap(ref s1.idx, ref s0.idx);
+            alglib.ap.swap(ref s1.uidx, ref s0.uidx);
+            alglib.ap.swap(ref s1.didx, ref s0.didx);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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 = s.tablesize;
+            if( (double)((1-maxloadfactor)*k)>=(double)(s.nfree) )
+            {
+                sparseresizematrix(s, _params);
+                k = s.tablesize;
+            }
+            hashcode = hash(i, j, k, _params);
+            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.
+
+        For Hash-based storage format:
+        * this function can be called at any moment - during matrix initialization
+          or later
+        * new value can be zero or non-zero.  In case new value of S[i,j] is zero,
+          this element is deleted from the table.
+        * this  function  has  no  effect when called with zero V for non-existent
+          element.
+
+        For CRS-bases storage format:
+        * this function can be called ONLY DURING MATRIX INITIALIZATION
+        * zero values are stored in the matrix similarly to non-zero ones
+        * elements must be initialized in correct order -  from top row to bottom,
+          within row - from left to right.
+          
+        For SKS storage:
+        * this function can be called at any moment - during matrix initialization
+          or later
+        * zero values are stored in the matrix similarly to non-zero ones
+        * this function CAN NOT be called for non-existent (outside  of  the  band
+          specified during SKS matrix creation) elements. Say, if you created  SKS
+          matrix  with  bandwidth=2  and  tried to call sparseset(s,0,10,VAL),  an
+          exception will be generated.
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in Hash-Table, SKS or CRS format.
+            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
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparseset(sparsematrix s,
+            int i,
+            int j,
+            double v,
+            alglib.xparams _params)
+        {
+            int hashcode = 0;
+            int tcode = 0;
+            int k = 0;
+            bool b = new bool();
+
+            alglib.ap.assert((s.matrixtype==0 || s.matrixtype==1) || s.matrixtype==2, "SparseSet: unsupported matrix storage format");
+            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 = s.tablesize;
+                if( (double)((1-maxloadfactor)*k)>=(double)(s.nfree) )
+                {
+                    sparseresizematrix(s, _params);
+                    k = s.tablesize;
+                }
+                hashcode = hash(i, j, k, _params);
+                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(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, _params);
+                }
+                return;
+            }
+            
+            //
+            // SKS matrix
+            //
+            if( s.matrixtype==2 )
+            {
+                b = sparserewriteexisting(s, i, j, v, _params);
+                alglib.ap.assert(b, "SparseSet: an attempt to initialize out-of-band element of the SKS matrix");
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function returns S[i,j] - element of the sparse matrix.  Matrix  can
+        be in any mode (Hash-Table, CRS, SKS), but this function is less efficient
+        for CRS matrices. Hash-Table and SKS matrices can find  element  in  O(1)
+        time, while  CRS  matrices need O(log(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,
+            alglib.xparams _params)
+        {
+            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");
+            result = 0.0;
+            if( s.matrixtype==0 )
+            {
+                
+                //
+                // Hash-based storage
+                //
+                result = 0;
+                k = s.tablesize;
+                hashcode = hash(i, j, k, _params);
+                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 )
+            {
+                
+                //
+                // CRS
+                //
+                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;
+                result = 0;
+                while( k0<=k1 )
+                {
+                    k = (k0+k1)/2;
+                    if( s.idx[k]==j )
+                    {
+                        result = s.vals[k];
+                        return result;
+                    }
+                    if( s.idx[k]<j )
+                    {
+                        k0 = k+1;
+                    }
+                    else
+                    {
+                        k1 = k-1;
+                    }
+                }
+                return result;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS
+                //
+                alglib.ap.assert(s.m==s.n, "SparseGet: non-square SKS matrix not supported");
+                result = 0;
+                if( i==j )
+                {
+                    
+                    //
+                    // Return diagonal element
+                    //
+                    result = s.vals[s.ridx[i]+s.didx[i]];
+                    return result;
+                }
+                if( j<i )
+                {
+                    
+                    //
+                    // Return subdiagonal element at I-th "skyline block"
+                    //
+                    k = s.didx[i];
+                    if( i-j<=k )
+                    {
+                        result = s.vals[s.ridx[i]+k+j-i];
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Return superdiagonal element at J-th "skyline block"
+                    //
+                    k = s.uidx[j];
+                    if( j-i<=k )
+                    {
+                        result = s.vals[s.ridx[j+1]-(j-i)];
+                    }
+                    return result;
+                }
+                return result;
+            }
+            alglib.ap.assert(false, "SparseGet: unexpected matrix type");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns I-th diagonal element of the sparse matrix.
+
+        Matrix can be in any mode (Hash-Table or CRS storage), but this  function
+        is most efficient for CRS matrices - it requires less than 50 CPU  cycles
+        to extract diagonal element. For Hash-Table matrices we still  have  O(1)
+        query time, but function is many times slower.
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in Hash-Table representation.
+                            Exception will be thrown for CRS matrix.
+            I           -   index of the element to modify, 0<=I<min(M,N)
+
+        RESULT
+            value of S[I,I] or zero (in case no element with such index is found)
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static double sparsegetdiagonal(sparsematrix s,
+            int i,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(i>=0, "SparseGetDiagonal: I<0");
+            alglib.ap.assert(i<s.m, "SparseGetDiagonal: I>=M");
+            alglib.ap.assert(i<s.n, "SparseGetDiagonal: I>=N");
+            result = 0;
+            if( s.matrixtype==0 )
+            {
+                result = sparseget(s, i, i, _params);
+                return result;
+            }
+            if( s.matrixtype==1 )
+            {
+                if( s.didx[i]!=s.uidx[i] )
+                {
+                    result = s.vals[s.didx[i]];
+                }
+                return result;
+            }
+            if( s.matrixtype==2 )
+            {
+                alglib.ap.assert(s.m==s.n, "SparseGetDiagonal: non-square SKS matrix not supported");
+                result = s.vals[s.ridx[i]+s.didx[i]];
+                return result;
+            }
+            alglib.ap.assert(false, "SparseGetDiagonal: unexpected matrix type");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-vector product  S*x.  Matrix  S  must  be
+        stored in CRS or SKS format (exception will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in CRS or SKS format.
+            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/SKS  matrix.
+        You must convert your matrix with SparseConvertToCRS/SKS()  before  using
+        this function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsemv(sparsematrix s,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            double tval = 0;
+            double v = 0;
+            double vv = 0;
+            int i = 0;
+            int j = 0;
+            int lt = 0;
+            int rt = 0;
+            int lt1 = 0;
+            int rt1 = 0;
+            int n = 0;
+            int m = 0;
+            int d = 0;
+            int u = 0;
+            int ri = 0;
+            int ri1 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=s.n, "SparseMV: length(X)<N");
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseMV: incorrect matrix type (convert your matrix to CRS/SKS)");
+            apserv.rvectorsetlengthatleast(ref y, s.m, _params);
+            n = s.n;
+            m = s.m;
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS format.
+                // Perform integrity check.
+                //
+                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)");
+                
+                //
+                // Try vendor kernels
+                //
+                if( ablasmkl.sparsegemvcrsmkl(0, s.m, s.n, 1.0, s.vals, s.idx, s.ridx, x, 0, 0.0, y, 0, _params) )
+                {
+                    return;
+                }
+                
+                //
+                // Our own implementation
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    tval = 0;
+                    lt = s.ridx[i];
+                    rt = s.ridx[i+1]-1;
+                    for(j=lt; j<=rt; j++)
+                    {
+                        tval = tval+x[s.idx[j]]*s.vals[j];
+                    }
+                    y[i] = tval;
+                }
+                return;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS format
+                //
+                alglib.ap.assert(s.m==s.n, "SparseMV: non-square SKS matrices are not supported");
+                for(i=0; i<=n-1; i++)
+                {
+                    ri = s.ridx[i];
+                    ri1 = s.ridx[i+1];
+                    d = s.didx[i];
+                    u = s.uidx[i];
+                    v = s.vals[ri+d]*x[i];
+                    if( d>0 )
+                    {
+                        lt = ri;
+                        rt = ri+d-1;
+                        lt1 = i-d;
+                        rt1 = i-1;
+                        i1_ = (lt1)-(lt);
+                        vv = 0.0;
+                        for(i_=lt; i_<=rt;i_++)
+                        {
+                            vv += s.vals[i_]*x[i_+i1_];
+                        }
+                        v = v+vv;
+                    }
+                    y[i] = v;
+                    if( u>0 )
+                    {
+                        lt = ri1-u;
+                        rt = ri1-1;
+                        lt1 = i-u;
+                        rt1 = i-1;
+                        v = x[i];
+                        i1_ = (lt) - (lt1);
+                        for(i_=lt1; i_<=rt1;i_++)
+                        {
+                            y[i_] = y[i_] + v*s.vals[i_+i1_];
+                        }
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-vector product  S^T*x. Matrix S  must  be
+        stored in CRS or SKS format (exception will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in CRS or SKS format.
+            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/SKS  matrix.
+        You must convert your matrix with SparseConvertToCRS/SKS()  before  using
+        this function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsemtv(sparsematrix s,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int lt = 0;
+            int rt = 0;
+            int ct = 0;
+            int lt1 = 0;
+            int rt1 = 0;
+            double v = 0;
+            double vv = 0;
+            int n = 0;
+            int m = 0;
+            int ri = 0;
+            int ri1 = 0;
+            int d = 0;
+            int u = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseMTV: incorrect matrix type (convert your matrix to CRS/SKS)");
+            alglib.ap.assert(alglib.ap.len(x)>=s.m, "SparseMTV: Length(X)<M");
+            n = s.n;
+            m = s.m;
+            apserv.rvectorsetlengthatleast(ref y, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                y[i] = 0;
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS format
+                // Perform integrity check.
+                //
+                alglib.ap.assert(s.ninitialized==s.ridx[m], "SparseMTV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+                
+                //
+                // Try vendor kernels
+                //
+                if( ablasmkl.sparsegemvcrsmkl(1, s.m, s.n, 1.0, s.vals, s.idx, s.ridx, x, 0, 0.0, y, 0, _params) )
+                {
+                    return;
+                }
+                
+                //
+                // Our own implementation
+                //
+                for(i=0; i<=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];
+                    }
+                }
+                return;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS format
+                //
+                alglib.ap.assert(s.m==s.n, "SparseMV: non-square SKS matrices are not supported");
+                for(i=0; i<=n-1; i++)
+                {
+                    ri = s.ridx[i];
+                    ri1 = s.ridx[i+1];
+                    d = s.didx[i];
+                    u = s.uidx[i];
+                    if( d>0 )
+                    {
+                        lt = ri;
+                        rt = ri+d-1;
+                        lt1 = i-d;
+                        rt1 = i-1;
+                        v = x[i];
+                        i1_ = (lt) - (lt1);
+                        for(i_=lt1; i_<=rt1;i_++)
+                        {
+                            y[i_] = y[i_] + v*s.vals[i_+i1_];
+                        }
+                    }
+                    v = s.vals[ri+d]*x[i];
+                    if( u>0 )
+                    {
+                        lt = ri1-u;
+                        rt = ri1-1;
+                        lt1 = i-u;
+                        rt1 = i-1;
+                        i1_ = (lt1)-(lt);
+                        vv = 0.0;
+                        for(i_=lt; i_<=rt;i_++)
+                        {
+                            vv += s.vals[i_]*x[i_+i1_];
+                        }
+                        v = v+vv;
+                    }
+                    y[i] = v;
+                }
+                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  or  SKS  format
+        (exception will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse N*N matrix in CRS or SKS format.
+            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/SKS  matrix.
+        You must convert your matrix with SparseConvertToCRS/SKS()  before  using
+        this function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsemv2(sparsematrix s,
+            double[] x,
+            ref double[] y0,
+            ref double[] y1,
+            alglib.xparams _params)
+        {
+            int l = 0;
+            double tval = 0;
+            int i = 0;
+            int j = 0;
+            double vx = 0;
+            double vs = 0;
+            double v = 0;
+            double vv = 0;
+            double vd0 = 0;
+            double vd1 = 0;
+            int vi = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int n = 0;
+            int ri = 0;
+            int ri1 = 0;
+            int d = 0;
+            int u = 0;
+            int lt = 0;
+            int rt = 0;
+            int lt1 = 0;
+            int rt1 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseMV2: incorrect matrix type (convert your matrix to CRS/SKS)");
+            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");
+            n = s.n;
+            apserv.rvectorsetlengthatleast(ref y0, l, _params);
+            apserv.rvectorsetlengthatleast(ref y1, l, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                y0[i] = 0;
+                y1[i] = 0;
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS format
+                //
+                alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseMV2: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+                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;
+                }
+                return;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS format
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    ri = s.ridx[i];
+                    ri1 = s.ridx[i+1];
+                    d = s.didx[i];
+                    u = s.uidx[i];
+                    vd0 = s.vals[ri+d]*x[i];
+                    vd1 = vd0;
+                    if( d>0 )
+                    {
+                        lt = ri;
+                        rt = ri+d-1;
+                        lt1 = i-d;
+                        rt1 = i-1;
+                        v = x[i];
+                        i1_ = (lt) - (lt1);
+                        for(i_=lt1; i_<=rt1;i_++)
+                        {
+                            y1[i_] = y1[i_] + v*s.vals[i_+i1_];
+                        }
+                        i1_ = (lt1)-(lt);
+                        vv = 0.0;
+                        for(i_=lt; i_<=rt;i_++)
+                        {
+                            vv += s.vals[i_]*x[i_+i1_];
+                        }
+                        vd0 = vd0+vv;
+                    }
+                    if( u>0 )
+                    {
+                        lt = ri1-u;
+                        rt = ri1-1;
+                        lt1 = i-u;
+                        rt1 = i-1;
+                        v = x[i];
+                        i1_ = (lt) - (lt1);
+                        for(i_=lt1; i_<=rt1;i_++)
+                        {
+                            y0[i_] = y0[i_] + v*s.vals[i_+i1_];
+                        }
+                        i1_ = (lt1)-(lt);
+                        vv = 0.0;
+                        for(i_=lt; i_<=rt;i_++)
+                        {
+                            vv += s.vals[i_]*x[i_+i1_];
+                        }
+                        vd1 = vd1+vv;
+                    }
+                    y0[i] = vd0;
+                    y1[i] = vd1;
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-vector product  S*x, when S is  symmetric
+        matrix. Matrix S  must be stored in CRS or SKS format  (exception will be
+        thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*M matrix in CRS or SKS format.
+            IsUpper     -   whether upper or lower triangle of S is given:
+                            * if upper triangle is given,  only   S[i,j] for j>=i
+                              are used, and lower triangle is ignored (it can  be
+                              empty - these elements are not referenced at all).
+                            * if lower triangle is given,  only   S[i,j] for j<=i
+                              are used, and upper triangle is ignored.
+            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/SKS  matrix.
+        You must convert your matrix with SparseConvertToCRS/SKS()  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,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int id = 0;
+            int lt = 0;
+            int rt = 0;
+            double v = 0;
+            double vv = 0;
+            double vy = 0;
+            double vx = 0;
+            double vd = 0;
+            int ri = 0;
+            int ri1 = 0;
+            int d = 0;
+            int u = 0;
+            int lt1 = 0;
+            int rt1 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseSMV: incorrect matrix type (convert your matrix to CRS/SKS)");
+            alglib.ap.assert(alglib.ap.len(x)>=s.n, "SparseSMV: length(X)<N");
+            alglib.ap.assert(s.m==s.n, "SparseSMV: non-square matrix");
+            n = s.n;
+            apserv.rvectorsetlengthatleast(ref y, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                y[i] = 0;
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS format
+                //
+                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)");
+                for(i=0; i<=n-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;
+                    }
+                }
+                return;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS format
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    ri = s.ridx[i];
+                    ri1 = s.ridx[i+1];
+                    d = s.didx[i];
+                    u = s.uidx[i];
+                    vd = s.vals[ri+d]*x[i];
+                    if( d>0 && !isupper )
+                    {
+                        lt = ri;
+                        rt = ri+d-1;
+                        lt1 = i-d;
+                        rt1 = i-1;
+                        v = x[i];
+                        i1_ = (lt) - (lt1);
+                        for(i_=lt1; i_<=rt1;i_++)
+                        {
+                            y[i_] = y[i_] + v*s.vals[i_+i1_];
+                        }
+                        i1_ = (lt1)-(lt);
+                        vv = 0.0;
+                        for(i_=lt; i_<=rt;i_++)
+                        {
+                            vv += s.vals[i_]*x[i_+i1_];
+                        }
+                        vd = vd+vv;
+                    }
+                    if( u>0 && isupper )
+                    {
+                        lt = ri1-u;
+                        rt = ri1-1;
+                        lt1 = i-u;
+                        rt1 = i-1;
+                        v = x[i];
+                        i1_ = (lt) - (lt1);
+                        for(i_=lt1; i_<=rt1;i_++)
+                        {
+                            y[i_] = y[i_] + v*s.vals[i_+i1_];
+                        }
+                        i1_ = (lt1)-(lt);
+                        vv = 0.0;
+                        for(i_=lt; i_<=rt;i_++)
+                        {
+                            vv += s.vals[i_]*x[i_+i1_];
+                        }
+                        vd = vd+vv;
+                    }
+                    y[i] = vd;
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates vector-matrix-vector product x'*S*x, where  S is
+        symmetric matrix. Matrix S must be stored in CRS or SKS format (exception
+        will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*M matrix in CRS or SKS format.
+            IsUpper     -   whether upper or lower triangle of S is given:
+                            * if upper triangle is given,  only   S[i,j] for j>=i
+                              are used, and lower triangle is ignored (it can  be
+                              empty - these elements are not referenced at all).
+                            * if lower triangle is given,  only   S[i,j] for j<=i
+                              are used, and upper triangle is ignored.
+            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.
+            
+        RESULT
+            x'*S*x
+            
+        NOTE: this function throws exception when called for non-CRS/SKS  matrix.
+        You must convert your matrix with SparseConvertToCRS/SKS()  before  using
+        this function.
+
+          -- ALGLIB PROJECT --
+             Copyright 27.01.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static double sparsevsmv(sparsematrix s,
+            bool isupper,
+            double[] x,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int id = 0;
+            int lt = 0;
+            int rt = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            int ri = 0;
+            int ri1 = 0;
+            int d = 0;
+            int u = 0;
+            int lt1 = 0;
+
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseVSMV: incorrect matrix type (convert your matrix to CRS/SKS)");
+            alglib.ap.assert(alglib.ap.len(x)>=s.n, "SparseVSMV: length(X)<N");
+            alglib.ap.assert(s.m==s.n, "SparseVSMV: non-square matrix");
+            n = s.n;
+            result = 0.0;
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS format
+                //
+                alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseVSMV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+                for(i=0; i<=n-1; i++)
+                {
+                    if( s.didx[i]!=s.uidx[i] )
+                    {
+                        v = x[s.idx[s.didx[i]]];
+                        result = result+v*s.vals[s.didx[i]]*v;
+                    }
+                    if( isupper )
+                    {
+                        lt = s.uidx[i];
+                        rt = s.ridx[i+1];
+                    }
+                    else
+                    {
+                        lt = s.ridx[i];
+                        rt = s.didx[i];
+                    }
+                    v0 = x[i];
+                    for(j=lt; j<=rt-1; j++)
+                    {
+                        id = s.idx[j];
+                        v1 = x[id];
+                        v = s.vals[j];
+                        result = result+2*v0*v1*v;
+                    }
+                }
+                return result;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS format
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    ri = s.ridx[i];
+                    ri1 = s.ridx[i+1];
+                    d = s.didx[i];
+                    u = s.uidx[i];
+                    v = x[i];
+                    result = result+v*s.vals[ri+d]*v;
+                    if( d>0 && !isupper )
+                    {
+                        lt = ri;
+                        rt = ri+d-1;
+                        lt1 = i-d;
+                        k = d-1;
+                        v0 = x[i];
+                        v = 0.0;
+                        for(j=0; j<=k; j++)
+                        {
+                            v = v+x[lt1+j]*s.vals[lt+j];
+                        }
+                        result = result+2*v0*v;
+                    }
+                    if( u>0 && isupper )
+                    {
+                        lt = ri1-u;
+                        rt = ri1-1;
+                        lt1 = i-u;
+                        k = u-1;
+                        v0 = x[i];
+                        v = 0.0;
+                        for(j=0; j<=k; j++)
+                        {
+                            v = v+x[lt1+j]*s.vals[lt+j];
+                        }
+                        result = result+2*v0*v;
+                    }
+                }
+                return result;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-matrix product  S*A.  Matrix  S  must  be
+        stored in CRS or SKS format (exception will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in CRS or SKS format.
+            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/SKS  matrix.
+        You must convert your matrix with SparseConvertToCRS/SKS()  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,
+            alglib.xparams _params)
+        {
+            double tval = 0;
+            double v = 0;
+            int id = 0;
+            int i = 0;
+            int j = 0;
+            int k0 = 0;
+            int k1 = 0;
+            int lt = 0;
+            int rt = 0;
+            int m = 0;
+            int n = 0;
+            int ri = 0;
+            int ri1 = 0;
+            int lt1 = 0;
+            int rt1 = 0;
+            int d = 0;
+            int u = 0;
+            double vd = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseMM: incorrect matrix type (convert your matrix to CRS/SKS)");
+            alglib.ap.assert(alglib.ap.rows(a)>=s.n, "SparseMM: Rows(A)<N");
+            alglib.ap.assert(k>0, "SparseMM: K<=0");
+            m = s.m;
+            n = s.n;
+            k1 = k-1;
+            apserv.rmatrixsetlengthatleast(ref b, m, k, _params);
+            for(i=0; i<=m-1; i++)
+            {
+                for(j=0; j<=k-1; j++)
+                {
+                    b[i,j] = 0;
+                }
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS format
+                //
+                alglib.ap.assert(s.ninitialized==s.ridx[m], "SparseMM: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+                if( k<linalgswitch )
+                {
+                    for(i=0; i<=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<=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_];
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS format
+                //
+                alglib.ap.assert(m==n, "SparseMM: non-square SKS matrices are not supported");
+                for(i=0; i<=n-1; i++)
+                {
+                    ri = s.ridx[i];
+                    ri1 = s.ridx[i+1];
+                    d = s.didx[i];
+                    u = s.uidx[i];
+                    if( d>0 )
+                    {
+                        lt = ri;
+                        rt = ri+d-1;
+                        lt1 = i-d;
+                        rt1 = i-1;
+                        for(j=lt1; j<=rt1; j++)
+                        {
+                            v = s.vals[lt+(j-lt1)];
+                            if( k<linalgswitch )
+                            {
+                                
+                                //
+                                // Use loop
+                                //
+                                for(k0=0; k0<=k1; k0++)
+                                {
+                                    b[i,k0] = b[i,k0]+v*a[j,k0];
+                                }
+                            }
+                            else
+                            {
+                                
+                                //
+                                // Use vector operation
+                                //
+                                for(i_=0; i_<=k-1;i_++)
+                                {
+                                    b[i,i_] = b[i,i_] + v*a[j,i_];
+                                }
+                            }
+                        }
+                    }
+                    if( u>0 )
+                    {
+                        lt = ri1-u;
+                        rt = ri1-1;
+                        lt1 = i-u;
+                        rt1 = i-1;
+                        for(j=lt1; j<=rt1; j++)
+                        {
+                            v = s.vals[lt+(j-lt1)];
+                            if( k<linalgswitch )
+                            {
+                                
+                                //
+                                // Use loop
+                                //
+                                for(k0=0; k0<=k1; k0++)
+                                {
+                                    b[j,k0] = b[j,k0]+v*a[i,k0];
+                                }
+                            }
+                            else
+                            {
+                                
+                                //
+                                // Use vector operation
+                                //
+                                for(i_=0; i_<=k-1;i_++)
+                                {
+                                    b[j,i_] = b[j,i_] + v*a[i,i_];
+                                }
+                            }
+                        }
+                    }
+                    vd = s.vals[ri+d];
+                    for(i_=0; i_<=k-1;i_++)
+                    {
+                        b[i,i_] = b[i,i_] + vd*a[i,i_];
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-matrix product  S^T*A. Matrix S  must  be
+        stored in CRS or SKS format (exception will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in CRS or SKS format.
+            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/SKS  matrix.
+        You must convert your matrix with SparseConvertToCRS/SKS()  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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k0 = 0;
+            int k1 = 0;
+            int lt = 0;
+            int rt = 0;
+            int ct = 0;
+            double v = 0;
+            int m = 0;
+            int n = 0;
+            int ri = 0;
+            int ri1 = 0;
+            int lt1 = 0;
+            int rt1 = 0;
+            int d = 0;
+            int u = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseMTM: incorrect matrix type (convert your matrix to CRS/SKS)");
+            alglib.ap.assert(alglib.ap.rows(a)>=s.m, "SparseMTM: Rows(A)<M");
+            alglib.ap.assert(k>0, "SparseMTM: K<=0");
+            m = s.m;
+            n = s.n;
+            k1 = k-1;
+            apserv.rmatrixsetlengthatleast(ref b, n, k, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=k-1; j++)
+                {
+                    b[i,j] = 0;
+                }
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS format
+                //
+                alglib.ap.assert(s.ninitialized==s.ridx[m], "SparseMTM: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+                if( k<linalgswitch )
+                {
+                    for(i=0; i<=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<=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_];
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS format
+                //
+                alglib.ap.assert(m==n, "SparseMTM: non-square SKS matrices are not supported");
+                for(i=0; i<=n-1; i++)
+                {
+                    ri = s.ridx[i];
+                    ri1 = s.ridx[i+1];
+                    d = s.didx[i];
+                    u = s.uidx[i];
+                    if( d>0 )
+                    {
+                        lt = ri;
+                        rt = ri+d-1;
+                        lt1 = i-d;
+                        rt1 = i-1;
+                        for(j=lt1; j<=rt1; j++)
+                        {
+                            v = s.vals[lt+(j-lt1)];
+                            if( k<linalgswitch )
+                            {
+                                
+                                //
+                                // Use loop
+                                //
+                                for(k0=0; k0<=k1; k0++)
+                                {
+                                    b[j,k0] = b[j,k0]+v*a[i,k0];
+                                }
+                            }
+                            else
+                            {
+                                
+                                //
+                                // Use vector operation
+                                //
+                                for(i_=0; i_<=k-1;i_++)
+                                {
+                                    b[j,i_] = b[j,i_] + v*a[i,i_];
+                                }
+                            }
+                        }
+                    }
+                    if( u>0 )
+                    {
+                        lt = ri1-u;
+                        rt = ri1-1;
+                        lt1 = i-u;
+                        rt1 = i-1;
+                        for(j=lt1; j<=rt1; j++)
+                        {
+                            v = s.vals[lt+(j-lt1)];
+                            if( k<linalgswitch )
+                            {
+                                
+                                //
+                                // Use loop
+                                //
+                                for(k0=0; k0<=k1; k0++)
+                                {
+                                    b[i,k0] = b[i,k0]+v*a[j,k0];
+                                }
+                            }
+                            else
+                            {
+                                
+                                //
+                                // Use vector operation
+                                //
+                                for(i_=0; i_<=k-1;i_++)
+                                {
+                                    b[i,i_] = b[i,i_] + v*a[j,i_];
+                                }
+                            }
+                        }
+                    }
+                    v = s.vals[ri+d];
+                    for(i_=0; i_<=k-1;i_++)
+                    {
+                        b[i,i_] = b[i,i_] + v*a[i,i_];
+                    }
+                }
+                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 or  SKS  format
+        (exception will be thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse N*N matrix in CRS or SKS format.
+            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/SKS  matrix.
+        You must convert your matrix with SparseConvertToCRS/SKS()  before  using
+        this function.
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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 n = 0;
+            int k1 = 0;
+            int ri = 0;
+            int ri1 = 0;
+            int lt1 = 0;
+            int rt1 = 0;
+            int d = 0;
+            int u = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseMM2: incorrect matrix type (convert your matrix to CRS/SKS)");
+            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");
+            n = s.n;
+            k1 = k-1;
+            apserv.rmatrixsetlengthatleast(ref b0, n, k, _params);
+            apserv.rmatrixsetlengthatleast(ref b1, n, k, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=k-1; j++)
+                {
+                    b1[i,j] = 0;
+                    b0[i,j] = 0;
+                }
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS format
+                //
+                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)");
+                if( k<linalgswitch )
+                {
+                    for(i=0; i<=n-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<=n-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_];
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS format
+                //
+                alglib.ap.assert(s.m==s.n, "SparseMM2: non-square SKS matrices are not supported");
+                for(i=0; i<=n-1; i++)
+                {
+                    ri = s.ridx[i];
+                    ri1 = s.ridx[i+1];
+                    d = s.didx[i];
+                    u = s.uidx[i];
+                    if( d>0 )
+                    {
+                        lt = ri;
+                        rt = ri+d-1;
+                        lt1 = i-d;
+                        rt1 = i-1;
+                        for(j=lt1; j<=rt1; j++)
+                        {
+                            v = s.vals[lt+(j-lt1)];
+                            if( k<linalgswitch )
+                            {
+                                
+                                //
+                                // Use loop
+                                //
+                                for(k0=0; k0<=k1; k0++)
+                                {
+                                    b0[i,k0] = b0[i,k0]+v*a[j,k0];
+                                    b1[j,k0] = b1[j,k0]+v*a[i,k0];
+                                }
+                            }
+                            else
+                            {
+                                
+                                //
+                                // Use vector operation
+                                //
+                                for(i_=0; i_<=k-1;i_++)
+                                {
+                                    b0[i,i_] = b0[i,i_] + v*a[j,i_];
+                                }
+                                for(i_=0; i_<=k-1;i_++)
+                                {
+                                    b1[j,i_] = b1[j,i_] + v*a[i,i_];
+                                }
+                            }
+                        }
+                    }
+                    if( u>0 )
+                    {
+                        lt = ri1-u;
+                        rt = ri1-1;
+                        lt1 = i-u;
+                        rt1 = i-1;
+                        for(j=lt1; j<=rt1; j++)
+                        {
+                            v = s.vals[lt+(j-lt1)];
+                            if( k<linalgswitch )
+                            {
+                                
+                                //
+                                // Use loop
+                                //
+                                for(k0=0; k0<=k1; k0++)
+                                {
+                                    b0[j,k0] = b0[j,k0]+v*a[i,k0];
+                                    b1[i,k0] = b1[i,k0]+v*a[j,k0];
+                                }
+                            }
+                            else
+                            {
+                                
+                                //
+                                // Use vector operation
+                                //
+                                for(i_=0; i_<=k-1;i_++)
+                                {
+                                    b0[j,i_] = b0[j,i_] + v*a[i,i_];
+                                }
+                                for(i_=0; i_<=k-1;i_++)
+                                {
+                                    b1[i,i_] = b1[i,i_] + v*a[j,i_];
+                                }
+                            }
+                        }
+                    }
+                    v = s.vals[ri+d];
+                    for(i_=0; i_<=k-1;i_++)
+                    {
+                        b0[i,i_] = b0[i,i_] + v*a[i,i_];
+                    }
+                    for(i_=0; i_<=k-1;i_++)
+                    {
+                        b1[i,i_] = b1[i,i_] + v*a[i,i_];
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-matrix product  S*A, when S  is  symmetric
+        matrix. Matrix S must be stored in CRS or SKS format  (exception  will  be
+        thrown otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse M*M matrix in CRS or SKS format.
+            IsUpper     -   whether upper or lower triangle of S is given:
+                            * if upper triangle is given,  only   S[i,j] for j>=i
+                              are used, and lower triangle is ignored (it can  be
+                              empty - these elements are not referenced at all).
+                            * if lower triangle is given,  only   S[i,j] for j<=i
+                              are used, and upper triangle is ignored.
+            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/SKS  matrix.
+        You must convert your matrix with SparseConvertToCRS/SKS()  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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k0 = 0;
+            int id = 0;
+            int k1 = 0;
+            int lt = 0;
+            int rt = 0;
+            double v = 0;
+            double vb = 0;
+            double va = 0;
+            int n = 0;
+            int ri = 0;
+            int ri1 = 0;
+            int lt1 = 0;
+            int rt1 = 0;
+            int d = 0;
+            int u = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseSMM: incorrect matrix type (convert your matrix to CRS/SKS)");
+            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");
+            n = s.n;
+            k1 = k-1;
+            apserv.rmatrixsetlengthatleast(ref b, n, k, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=k-1; j++)
+                {
+                    b[i,j] = 0;
+                }
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS format
+                //
+                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)");
+                if( k>linalgswitch )
+                {
+                    for(i=0; i<=n-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<=n-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_];
+                                }
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS format
+                //
+                alglib.ap.assert(s.m==s.n, "SparseMM2: non-square SKS matrices are not supported");
+                for(i=0; i<=n-1; i++)
+                {
+                    ri = s.ridx[i];
+                    ri1 = s.ridx[i+1];
+                    d = s.didx[i];
+                    u = s.uidx[i];
+                    if( d>0 && !isupper )
+                    {
+                        lt = ri;
+                        rt = ri+d-1;
+                        lt1 = i-d;
+                        rt1 = i-1;
+                        for(j=lt1; j<=rt1; j++)
+                        {
+                            v = s.vals[lt+(j-lt1)];
+                            if( k<linalgswitch )
+                            {
+                                
+                                //
+                                // Use loop
+                                //
+                                for(k0=0; k0<=k1; k0++)
+                                {
+                                    b[i,k0] = b[i,k0]+v*a[j,k0];
+                                    b[j,k0] = b[j,k0]+v*a[i,k0];
+                                }
+                            }
+                            else
+                            {
+                                
+                                //
+                                // Use vector operation
+                                //
+                                for(i_=0; i_<=k-1;i_++)
+                                {
+                                    b[i,i_] = b[i,i_] + v*a[j,i_];
+                                }
+                                for(i_=0; i_<=k-1;i_++)
+                                {
+                                    b[j,i_] = b[j,i_] + v*a[i,i_];
+                                }
+                            }
+                        }
+                    }
+                    if( u>0 && isupper )
+                    {
+                        lt = ri1-u;
+                        rt = ri1-1;
+                        lt1 = i-u;
+                        rt1 = i-1;
+                        for(j=lt1; j<=rt1; j++)
+                        {
+                            v = s.vals[lt+(j-lt1)];
+                            if( k<linalgswitch )
+                            {
+                                
+                                //
+                                // Use loop
+                                //
+                                for(k0=0; k0<=k1; k0++)
+                                {
+                                    b[j,k0] = b[j,k0]+v*a[i,k0];
+                                    b[i,k0] = b[i,k0]+v*a[j,k0];
+                                }
+                            }
+                            else
+                            {
+                                
+                                //
+                                // Use vector operation
+                                //
+                                for(i_=0; i_<=k-1;i_++)
+                                {
+                                    b[j,i_] = b[j,i_] + v*a[i,i_];
+                                }
+                                for(i_=0; i_<=k-1;i_++)
+                                {
+                                    b[i,i_] = b[i,i_] + v*a[j,i_];
+                                }
+                            }
+                        }
+                    }
+                    v = s.vals[ri+d];
+                    for(i_=0; i_<=k-1;i_++)
+                    {
+                        b[i,i_] = b[i,i_] + v*a[i,i_];
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates matrix-vector product op(S)*x, when x is  vector,
+        S is symmetric triangular matrix, op(S) is transposition or no  operation.
+        Matrix S must be stored in CRS or SKS format  (exception  will  be  thrown
+        otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse square matrix in CRS or SKS format.
+            IsUpper     -   whether upper or lower triangle of S is used:
+                            * if upper triangle is given,  only   S[i,j] for  j>=i
+                              are used, and lower triangle is  ignored (it can  be
+                              empty - these elements are not referenced at all).
+                            * if lower triangle is given,  only   S[i,j] for  j<=i
+                              are used, and upper triangle is ignored.
+            IsUnit      -   unit or non-unit diagonal:
+                            * if True, diagonal elements of triangular matrix  are
+                              considered equal to 1.0. Actual elements  stored  in
+                              S are not referenced at all.
+                            * if False, diagonal stored in S is used
+            OpType      -   operation type:
+                            * if 0, S*x is calculated
+                            * if 1, (S^T)*x is calculated (transposition)
+            X           -   array[N] which stores 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           -   possibly  preallocated  input   buffer.  Automatically 
+                            resized if its size is too small.
+            
+        OUTPUT PARAMETERS
+            Y           -   array[N], op(S)*x
+            
+        NOTE: this function throws exception when called for non-CRS/SKS  matrix.
+        You must convert your matrix with SparseConvertToCRS/SKS()  before  using
+        this function.
+
+          -- ALGLIB PROJECT --
+             Copyright 20.01.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsetrmv(sparsematrix s,
+            bool isupper,
+            bool isunit,
+            int optype,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int j0 = 0;
+            int j1 = 0;
+            double v = 0;
+            int ri = 0;
+            int ri1 = 0;
+            int d = 0;
+            int u = 0;
+            int lt = 0;
+            int rt = 0;
+            int lt1 = 0;
+            int rt1 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseTRMV: incorrect matrix type (convert your matrix to CRS/SKS)");
+            alglib.ap.assert(optype==0 || optype==1, "SparseTRMV: incorrect operation type (must be 0 or 1)");
+            alglib.ap.assert(alglib.ap.len(x)>=s.n, "SparseTRMV: Length(X)<N");
+            alglib.ap.assert(s.m==s.n, "SparseTRMV: matrix is non-square");
+            n = s.n;
+            apserv.rvectorsetlengthatleast(ref y, n, _params);
+            if( isunit )
+            {
+                
+                //
+                // Set initial value of y to x
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    y[i] = x[i];
+                }
+            }
+            else
+            {
+                
+                //
+                // Set initial value of y to 0
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    y[i] = 0;
+                }
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS format
+                //
+                alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseTRMV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Depending on IsUpper/IsUnit, select range of indexes to process
+                    //
+                    if( isupper )
+                    {
+                        if( isunit || s.didx[i]==s.uidx[i] )
+                        {
+                            j0 = s.uidx[i];
+                        }
+                        else
+                        {
+                            j0 = s.didx[i];
+                        }
+                        j1 = s.ridx[i+1]-1;
+                    }
+                    else
+                    {
+                        j0 = s.ridx[i];
+                        if( isunit || s.didx[i]==s.uidx[i] )
+                        {
+                            j1 = s.didx[i]-1;
+                        }
+                        else
+                        {
+                            j1 = s.didx[i];
+                        }
+                    }
+                    
+                    //
+                    // Depending on OpType, process subset of I-th row of input matrix
+                    //
+                    if( optype==0 )
+                    {
+                        v = 0.0;
+                        for(j=j0; j<=j1; j++)
+                        {
+                            v = v+s.vals[j]*x[s.idx[j]];
+                        }
+                        y[i] = y[i]+v;
+                    }
+                    else
+                    {
+                        v = x[i];
+                        for(j=j0; j<=j1; j++)
+                        {
+                            k = s.idx[j];
+                            y[k] = y[k]+v*s.vals[j];
+                        }
+                    }
+                }
+                return;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS format
+                //
+                alglib.ap.assert(s.m==s.n, "SparseTRMV: non-square SKS matrices are not supported");
+                for(i=0; i<=n-1; i++)
+                {
+                    ri = s.ridx[i];
+                    ri1 = s.ridx[i+1];
+                    d = s.didx[i];
+                    u = s.uidx[i];
+                    if( !isunit )
+                    {
+                        y[i] = y[i]+s.vals[ri+d]*x[i];
+                    }
+                    if( d>0 && !isupper )
+                    {
+                        lt = ri;
+                        rt = ri+d-1;
+                        lt1 = i-d;
+                        rt1 = i-1;
+                        if( optype==0 )
+                        {
+                            i1_ = (lt1)-(lt);
+                            v = 0.0;
+                            for(i_=lt; i_<=rt;i_++)
+                            {
+                                v += s.vals[i_]*x[i_+i1_];
+                            }
+                            y[i] = y[i]+v;
+                        }
+                        else
+                        {
+                            v = x[i];
+                            i1_ = (lt) - (lt1);
+                            for(i_=lt1; i_<=rt1;i_++)
+                            {
+                                y[i_] = y[i_] + v*s.vals[i_+i1_];
+                            }
+                        }
+                    }
+                    if( u>0 && isupper )
+                    {
+                        lt = ri1-u;
+                        rt = ri1-1;
+                        lt1 = i-u;
+                        rt1 = i-1;
+                        if( optype==0 )
+                        {
+                            v = x[i];
+                            i1_ = (lt) - (lt1);
+                            for(i_=lt1; i_<=rt1;i_++)
+                            {
+                                y[i_] = y[i_] + v*s.vals[i_+i1_];
+                            }
+                        }
+                        else
+                        {
+                            i1_ = (lt1)-(lt);
+                            v = 0.0;
+                            for(i_=lt; i_<=rt;i_++)
+                            {
+                                v += s.vals[i_]*x[i_+i1_];
+                            }
+                            y[i] = y[i]+v;
+                        }
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function solves linear system op(S)*y=x  where  x  is  vector,  S  is
+        symmetric  triangular  matrix,  op(S)  is  transposition  or no operation.
+        Matrix S must be stored in CRS or SKS format  (exception  will  be  thrown
+        otherwise).
+
+        INPUT PARAMETERS
+            S           -   sparse square matrix in CRS or SKS format.
+            IsUpper     -   whether upper or lower triangle of S is used:
+                            * if upper triangle is given,  only   S[i,j] for  j>=i
+                              are used, and lower triangle is  ignored (it can  be
+                              empty - these elements are not referenced at all).
+                            * if lower triangle is given,  only   S[i,j] for  j<=i
+                              are used, and upper triangle is ignored.
+            IsUnit      -   unit or non-unit diagonal:
+                            * if True, diagonal elements of triangular matrix  are
+                              considered equal to 1.0. Actual elements  stored  in
+                              S are not referenced at all.
+                            * if False, diagonal stored in S is used. It  is  your
+                              responsibility  to  make  sure  that   diagonal   is
+                              non-zero.
+            OpType      -   operation type:
+                            * if 0, S*x is calculated
+                            * if 1, (S^T)*x is calculated (transposition)
+            X           -   array[N] which stores 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.
+            
+        OUTPUT PARAMETERS
+            X           -   array[N], inv(op(S))*x
+            
+        NOTE: this function throws exception when called for  non-CRS/SKS  matrix.
+              You must convert your matrix  with  SparseConvertToCRS/SKS()  before
+              using this function.
+
+        NOTE: no assertion or tests are done during algorithm  operation.   It  is
+              your responsibility to provide invertible matrix to algorithm.
+
+          -- ALGLIB PROJECT --
+             Copyright 20.01.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsetrsv(sparsematrix s,
+            bool isupper,
+            bool isunit,
+            int optype,
+            double[] x,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int fst = 0;
+            int lst = 0;
+            int stp = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            double vd = 0;
+            double v0 = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int ri = 0;
+            int ri1 = 0;
+            int d = 0;
+            int u = 0;
+            int lt = 0;
+            int lt1 = 0;
+
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseTRSV: incorrect matrix type (convert your matrix to CRS/SKS)");
+            alglib.ap.assert(optype==0 || optype==1, "SparseTRSV: incorrect operation type (must be 0 or 1)");
+            alglib.ap.assert(alglib.ap.len(x)>=s.n, "SparseTRSV: Length(X)<N");
+            alglib.ap.assert(s.m==s.n, "SparseTRSV: matrix is non-square");
+            n = s.n;
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS format.
+                //
+                // Several branches for different combinations of IsUpper and OpType
+                //
+                alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseTRSV: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+                if( optype==0 )
+                {
+                    
+                    //
+                    // No transposition.
+                    //
+                    // S*x=y with upper or lower triangular S.
+                    //
+                    v0 = 0;
+                    if( isupper )
+                    {
+                        fst = n-1;
+                        lst = 0;
+                        stp = -1;
+                    }
+                    else
+                    {
+                        fst = 0;
+                        lst = n-1;
+                        stp = 1;
+                    }
+                    i = fst;
+                    while( (stp>0 && i<=lst) || (stp<0 && i>=lst) )
+                    {
+                        
+                        //
+                        // Select range of indexes to process
+                        //
+                        if( isupper )
+                        {
+                            j0 = s.uidx[i];
+                            j1 = s.ridx[i+1]-1;
+                        }
+                        else
+                        {
+                            j0 = s.ridx[i];
+                            j1 = s.didx[i]-1;
+                        }
+                        
+                        //
+                        // Calculate X[I]
+                        //
+                        v = 0.0;
+                        for(j=j0; j<=j1; j++)
+                        {
+                            v = v+s.vals[j]*x[s.idx[j]];
+                        }
+                        if( !isunit )
+                        {
+                            if( s.didx[i]==s.uidx[i] )
+                            {
+                                vd = 0;
+                            }
+                            else
+                            {
+                                vd = s.vals[s.didx[i]];
+                            }
+                        }
+                        else
+                        {
+                            vd = 1.0;
+                        }
+                        v = (x[i]-v)/vd;
+                        x[i] = v;
+                        v0 = 0.25*v0+v;
+                        
+                        //
+                        // Next I
+                        //
+                        i = i+stp;
+                    }
+                    alglib.ap.assert(math.isfinite(v0), "SparseTRSV: overflow or division by exact zero");
+                    return;
+                }
+                if( optype==1 )
+                {
+                    
+                    //
+                    // Transposition.
+                    //
+                    // (S^T)*x=y with upper or lower triangular S.
+                    //
+                    if( isupper )
+                    {
+                        fst = 0;
+                        lst = n-1;
+                        stp = 1;
+                    }
+                    else
+                    {
+                        fst = n-1;
+                        lst = 0;
+                        stp = -1;
+                    }
+                    i = fst;
+                    v0 = 0;
+                    while( (stp>0 && i<=lst) || (stp<0 && i>=lst) )
+                    {
+                        v = x[i];
+                        if( v!=0.0 )
+                        {
+                            
+                            //
+                            // X[i] already stores A[i,i]*Y[i], the only thing left
+                            // is to divide by diagonal element.
+                            //
+                            if( !isunit )
+                            {
+                                if( s.didx[i]==s.uidx[i] )
+                                {
+                                    vd = 0;
+                                }
+                                else
+                                {
+                                    vd = s.vals[s.didx[i]];
+                                }
+                            }
+                            else
+                            {
+                                vd = 1.0;
+                            }
+                            v = v/vd;
+                            x[i] = v;
+                            v0 = 0.25*v0+v;
+                            
+                            //
+                            // For upper triangular case:
+                            //     subtract X[i]*Ai from X[i+1:N-1]
+                            //
+                            // For lower triangular case:
+                            //     subtract X[i]*Ai from X[0:i-1]
+                            //
+                            // (here Ai is I-th row of original, untransposed A).
+                            //
+                            if( isupper )
+                            {
+                                j0 = s.uidx[i];
+                                j1 = s.ridx[i+1]-1;
+                            }
+                            else
+                            {
+                                j0 = s.ridx[i];
+                                j1 = s.didx[i]-1;
+                            }
+                            for(j=j0; j<=j1; j++)
+                            {
+                                k = s.idx[j];
+                                x[k] = x[k]-s.vals[j]*v;
+                            }
+                        }
+                        
+                        //
+                        // Next I
+                        //
+                        i = i+stp;
+                    }
+                    alglib.ap.assert(math.isfinite(v0), "SparseTRSV: overflow or division by exact zero");
+                    return;
+                }
+                alglib.ap.assert(false, "SparseTRSV: internal error");
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS format
+                //
+                alglib.ap.assert(s.m==s.n, "SparseTRSV: non-square SKS matrices are not supported");
+                if( (optype==0 && !isupper) || (optype==1 && isupper) )
+                {
+                    
+                    //
+                    // Lower triangular op(S) (matrix itself can be upper triangular).
+                    //
+                    v0 = 0;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        
+                        //
+                        // Select range of indexes to process
+                        //
+                        ri = s.ridx[i];
+                        ri1 = s.ridx[i+1];
+                        d = s.didx[i];
+                        u = s.uidx[i];
+                        if( isupper )
+                        {
+                            lt = i-u;
+                            lt1 = ri1-u;
+                            k = u-1;
+                        }
+                        else
+                        {
+                            lt = i-d;
+                            lt1 = ri;
+                            k = d-1;
+                        }
+                        
+                        //
+                        // Calculate X[I]
+                        //
+                        v = 0.0;
+                        for(j=0; j<=k; j++)
+                        {
+                            v = v+s.vals[lt1+j]*x[lt+j];
+                        }
+                        if( isunit )
+                        {
+                            vd = 1;
+                        }
+                        else
+                        {
+                            vd = s.vals[ri+d];
+                        }
+                        v = (x[i]-v)/vd;
+                        x[i] = v;
+                        v0 = 0.25*v0+v;
+                    }
+                    alglib.ap.assert(math.isfinite(v0), "SparseTRSV: overflow or division by exact zero");
+                    return;
+                }
+                if( (optype==1 && !isupper) || (optype==0 && isupper) )
+                {
+                    
+                    //
+                    // Upper triangular op(S) (matrix itself can be lower triangular).
+                    //
+                    v0 = 0;
+                    for(i=n-1; i>=0; i--)
+                    {
+                        ri = s.ridx[i];
+                        ri1 = s.ridx[i+1];
+                        d = s.didx[i];
+                        u = s.uidx[i];
+                        
+                        //
+                        // X[i] already stores A[i,i]*Y[i], the only thing left
+                        // is to divide by diagonal element.
+                        //
+                        if( isunit )
+                        {
+                            vd = 1;
+                        }
+                        else
+                        {
+                            vd = s.vals[ri+d];
+                        }
+                        v = x[i]/vd;
+                        x[i] = v;
+                        v0 = 0.25*v0+v;
+                        
+                        //
+                        // Subtract product of X[i] and I-th column of "effective" A from
+                        // unprocessed variables.
+                        //
+                        v = x[i];
+                        if( isupper )
+                        {
+                            lt = i-u;
+                            lt1 = ri1-u;
+                            k = u-1;
+                        }
+                        else
+                        {
+                            lt = i-d;
+                            lt1 = ri;
+                            k = d-1;
+                        }
+                        for(j=0; j<=k; j++)
+                        {
+                            x[lt+j] = x[lt+j]-v*s.vals[lt1+j];
+                        }
+                    }
+                    alglib.ap.assert(math.isfinite(v0), "SparseTRSV: overflow or division by exact zero");
+                    return;
+                }
+                alglib.ap.assert(false, "SparseTRSV: internal error");
+            }
+            alglib.ap.assert(false, "SparseTRSV: internal error");
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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 = s.tablesize;
+            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.tablesize = (int)Math.Round(k1/desiredloadfactor*growfactor+additional);
+            s.nfree = s.tablesize-k1;
+            tvals = new double[s.tablesize];
+            tidx = new int[2*s.tablesize];
+            alglib.ap.swap(ref s.vals, ref tvals);
+            alglib.ap.swap(ref s.idx, ref tidx);
+            for(i=0; i<=s.tablesize-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], _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Procedure for initialization 'S.DIdx' and 'S.UIdx'
+
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparseinitduidx(sparsematrix s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int lt = 0;
+            int rt = 0;
+
+            alglib.ap.assert(s.matrixtype==1, "SparseInitDUIdx: internal error, incorrect matrix type");
+            apserv.ivectorsetlengthatleast(ref s.didx, s.m, _params);
+            apserv.ivectorsetlengthatleast(ref s.uidx, s.m, _params);
+            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++)
+                {
+                    k = s.idx[j];
+                    if( k==i )
+                    {
+                        s.didx[i] = j;
+                    }
+                    else
+                    {
+                        if( k>i && s.uidx[i]==-1 )
+                        {
+                            s.uidx[i] = j;
+                            break;
+                        }
+                    }
+                }
+                if( s.uidx[i]==-1 )
+                {
+                    s.uidx[i] = s.ridx[i+1];
+                }
+                if( s.didx[i]==-1 )
+                {
+                    s.didx[i] = s.uidx[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,
+            alglib.xparams _params)
+        {
+            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!=0 )
+            {
+                result = 0;
+                return result;
+            }
+            nchains = 0;
+            talc = 0;
+            l = s.tablesize;
+            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, _params);
+                    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;
+        }
+
+
+        /*************************************************************************
+        This  function  is  used  to enumerate all elements of the sparse matrix.
+        Before  first  call  user  initializes  T0 and T1 counters by zero. These
+        counters are used to remember current position in a  matrix;  after  each
+        call they are updated by the function.
+
+        Subsequent calls to this function return non-zero elements of the  sparse
+        matrix, one by one. If you enumerate CRS matrix, matrix is traversed from
+        left to right, from top to bottom. In case you enumerate matrix stored as
+        Hash table, elements are returned in random order.
+
+        EXAMPLE
+            > T0=0
+            > T1=0
+            > while SparseEnumerate(S,T0,T1,I,J,V) do
+            >     ....do something with I,J,V
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in Hash-Table or CRS representation.
+            T0          -   internal counter
+            T1          -   internal counter
+            
+        OUTPUT PARAMETERS
+            T0          -   new value of the internal counter
+            T1          -   new value of the internal counter
+            I           -   row index of non-zero element, 0<=I<M.
+            J           -   column index of non-zero element, 0<=J<N
+            V           -   value of the T-th element
+            
+        RESULT
+            True in case of success (next non-zero element was retrieved)
+            False in case all non-zero elements were enumerated
+            
+        NOTE: you may call SparseRewriteExisting() during enumeration, but it  is
+              THE  ONLY  matrix  modification  function  you  can  call!!!  Other
+              matrix modification functions should not be called during enumeration!
+
+          -- ALGLIB PROJECT --
+             Copyright 14.03.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool sparseenumerate(sparsematrix s,
+            ref int t0,
+            ref int t1,
+            ref int i,
+            ref int j,
+            ref double v,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int sz = 0;
+            int i0 = 0;
+
+            i = 0;
+            j = 0;
+            v = 0;
+
+            result = false;
+            if( t0<0 || (s.matrixtype!=0 && t1<0) )
+            {
+                
+                //
+                // Incorrect T0/T1, terminate enumeration
+                //
+                result = false;
+                return result;
+            }
+            if( s.matrixtype==0 )
+            {
+                
+                //
+                // Hash-table matrix
+                //
+                sz = s.tablesize;
+                for(i0=t0; i0<=sz-1; i0++)
+                {
+                    if( s.idx[2*i0]==-1 || s.idx[2*i0]==-2 )
+                    {
+                        continue;
+                    }
+                    else
+                    {
+                        i = s.idx[2*i0];
+                        j = s.idx[2*i0+1];
+                        v = s.vals[i0];
+                        t0 = i0+1;
+                        result = true;
+                        return result;
+                    }
+                }
+                t0 = 0;
+                t1 = 0;
+                result = false;
+                return result;
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS matrix
+                //
+                alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseEnumerate: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+                if( t0>=s.ninitialized )
+                {
+                    t0 = 0;
+                    t1 = 0;
+                    result = false;
+                    return result;
+                }
+                while( t0>s.ridx[t1+1]-1 && t1<s.m )
+                {
+                    t1 = t1+1;
+                }
+                i = t1;
+                j = s.idx[t0];
+                v = s.vals[t0];
+                t0 = t0+1;
+                result = true;
+                return result;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS matrix:
+                // * T0 stores current offset in Vals[] array
+                // * T1 stores index of the diagonal block
+                //
+                alglib.ap.assert(s.m==s.n, "SparseEnumerate: non-square SKS matrices are not supported");
+                if( t0>=s.ridx[s.m] )
+                {
+                    t0 = 0;
+                    t1 = 0;
+                    result = false;
+                    return result;
+                }
+                while( t0>s.ridx[t1+1]-1 && t1<s.m )
+                {
+                    t1 = t1+1;
+                }
+                i0 = t0-s.ridx[t1];
+                if( i0<s.didx[t1]+1 )
+                {
+                    
+                    //
+                    // subdiagonal or diagonal element, row index is T1.
+                    //
+                    i = t1;
+                    j = t1-s.didx[t1]+i0;
+                }
+                else
+                {
+                    
+                    //
+                    // superdiagonal element, column index is T1.
+                    //
+                    i = t1-(s.ridx[t1+1]-t0);
+                    j = t1;
+                }
+                v = s.vals[t0];
+                t0 = t0+1;
+                result = true;
+                return result;
+            }
+            alglib.ap.assert(false, "SparseEnumerate: unexpected matrix type");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function rewrites existing (non-zero) element. It  returns  True   if
+        element  exists  or  False,  when  it  is  called for non-existing  (zero)
+        element.
+
+        This function works with any kind of the matrix.
+
+        The purpose of this function is to provide convenient thread-safe  way  to
+        modify  sparse  matrix.  Such  modification  (already  existing element is
+        rewritten) is guaranteed to be thread-safe without any synchronization, as
+        long as different threads modify different elements.
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in any kind of representation
+                            (Hash, SKS, CRS).
+            I           -   row index of non-zero element to modify, 0<=I<M
+            J           -   column index of non-zero element to modify, 0<=J<N
+            V           -   value to rewrite, must be finite number
+
+        OUTPUT PARAMETERS
+            S           -   modified matrix
+        RESULT
+            True in case when element exists
+            False in case when element doesn't exist or it is zero
+            
+          -- ALGLIB PROJECT --
+             Copyright 14.03.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool sparserewriteexisting(sparsematrix s,
+            int i,
+            int j,
+            double v,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int hashcode = 0;
+            int k = 0;
+            int k0 = 0;
+            int k1 = 0;
+
+            alglib.ap.assert(0<=i && i<s.m, "SparseRewriteExisting: invalid argument I(either I<0 or I>=S.M)");
+            alglib.ap.assert(0<=j && j<s.n, "SparseRewriteExisting: invalid argument J(either J<0 or J>=S.N)");
+            alglib.ap.assert(math.isfinite(v), "SparseRewriteExisting: invalid argument V(either V is infinite or V is NaN)");
+            result = false;
+            
+            //
+            // Hash-table matrix
+            //
+            if( s.matrixtype==0 )
+            {
+                k = s.tablesize;
+                hashcode = hash(i, j, k, _params);
+                while( true )
+                {
+                    if( s.idx[2*hashcode]==-1 )
+                    {
+                        return result;
+                    }
+                    if( s.idx[2*hashcode]==i && s.idx[2*hashcode+1]==j )
+                    {
+                        s.vals[hashcode] = v;
+                        result = true;
+                        return result;
+                    }
+                    hashcode = (hashcode+1)%k;
+                }
+            }
+            
+            //
+            // CRS matrix
+            //
+            if( s.matrixtype==1 )
+            {
+                alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseRewriteExisting: 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;
+                while( k0<=k1 )
+                {
+                    k = (k0+k1)/2;
+                    if( s.idx[k]==j )
+                    {
+                        s.vals[k] = v;
+                        result = true;
+                        return result;
+                    }
+                    if( s.idx[k]<j )
+                    {
+                        k0 = k+1;
+                    }
+                    else
+                    {
+                        k1 = k-1;
+                    }
+                }
+            }
+            
+            //
+            // SKS
+            //
+            if( s.matrixtype==2 )
+            {
+                alglib.ap.assert(s.m==s.n, "SparseRewriteExisting: non-square SKS matrix not supported");
+                if( i==j )
+                {
+                    
+                    //
+                    // Rewrite diagonal element
+                    //
+                    result = true;
+                    s.vals[s.ridx[i]+s.didx[i]] = v;
+                    return result;
+                }
+                if( j<i )
+                {
+                    
+                    //
+                    // Return subdiagonal element at I-th "skyline block"
+                    //
+                    k = s.didx[i];
+                    if( i-j<=k )
+                    {
+                        s.vals[s.ridx[i]+k+j-i] = v;
+                        result = true;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Return superdiagonal element at J-th "skyline block"
+                    //
+                    k = s.uidx[j];
+                    if( j-i<=k )
+                    {
+                        s.vals[s.ridx[j+1]-(j-i)] = v;
+                        result = true;
+                    }
+                }
+                return result;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns I-th row of the sparse matrix. Matrix must be stored
+        in CRS or SKS format.
+
+        INPUT PARAMETERS:
+            S           -   sparse M*N matrix in CRS format
+            I           -   row index, 0<=I<M
+            IRow        -   output buffer, can be  preallocated.  In  case  buffer
+                            size  is  too  small  to  store  I-th   row,   it   is
+                            automatically reallocated.
+         
+        OUTPUT PARAMETERS:
+            IRow        -   array[M], I-th row.
+            
+        NOTE: this function has O(N) running time, where N is a  column  count. It
+              allocates and fills N-element  array,  even  although  most  of  its
+              elemets are zero.
+              
+        NOTE: If you have O(non-zeros-per-row) time and memory  requirements,  use
+              SparseGetCompressedRow() function. It  returns  data  in  compressed
+              format.
+
+        NOTE: when  incorrect  I  (outside  of  [0,M-1]) or  matrix (non  CRS/SKS)
+              is passed, this function throws exception.
+
+          -- ALGLIB PROJECT --
+             Copyright 10.12.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsegetrow(sparsematrix s,
+            int i,
+            ref double[] irow,
+            alglib.xparams _params)
+        {
+            int i0 = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int j = 0;
+            int upperprofile = 0;
+
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseGetRow: S must be CRS/SKS-based matrix");
+            alglib.ap.assert(i>=0 && i<s.m, "SparseGetRow: I<0 or I>=M");
+            
+            //
+            // Prepare output buffer
+            //
+            apserv.rvectorsetlengthatleast(ref irow, s.n, _params);
+            for(i0=0; i0<=s.n-1; i0++)
+            {
+                irow[i0] = 0;
+            }
+            
+            //
+            // Output
+            //
+            if( s.matrixtype==1 )
+            {
+                for(i0=s.ridx[i]; i0<=s.ridx[i+1]-1; i0++)
+                {
+                    irow[s.idx[i0]] = s.vals[i0];
+                }
+                return;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // Copy subdiagonal and diagonal parts
+                //
+                alglib.ap.assert(s.n==s.m, "SparseGetRow: non-square SKS matrices are not supported");
+                j0 = i-s.didx[i];
+                i0 = -j0+s.ridx[i];
+                for(j=j0; j<=i; j++)
+                {
+                    irow[j] = s.vals[j+i0];
+                }
+                
+                //
+                // Copy superdiagonal part
+                //
+                upperprofile = s.uidx[s.n];
+                j0 = i+1;
+                j1 = Math.Min(s.n-1, i+upperprofile);
+                for(j=j0; j<=j1; j++)
+                {
+                    if( j-i<=s.uidx[j] )
+                    {
+                        irow[j] = s.vals[s.ridx[j+1]-(j-i)];
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function returns I-th row of the sparse matrix IN COMPRESSED FORMAT -
+        only non-zero elements are returned (with their indexes). Matrix  must  be
+        stored in CRS or SKS format.
+
+        INPUT PARAMETERS:
+            S           -   sparse M*N matrix in CRS format
+            I           -   row index, 0<=I<M
+            ColIdx      -   output buffer for column indexes, can be preallocated.
+                            In case buffer size is too small to store I-th row, it
+                            is automatically reallocated.
+            Vals        -   output buffer for values, can be preallocated. In case
+                            buffer size is too small to  store  I-th  row,  it  is
+                            automatically reallocated.
+         
+        OUTPUT PARAMETERS:
+            ColIdx      -   column   indexes   of  non-zero  elements,  sorted  by
+                            ascending. Symbolically non-zero elements are  counted
+                            (i.e. if you allocated place for element, but  it  has
+                            zero numerical value - it is counted).
+            Vals        -   values. Vals[K] stores value of  matrix  element  with
+                            indexes (I,ColIdx[K]). Symbolically non-zero  elements
+                            are counted (i.e. if you allocated place for  element,
+                            but it has zero numerical value - it is counted).
+            NZCnt       -   number of symbolically non-zero elements per row.
+
+        NOTE: when  incorrect  I  (outside  of  [0,M-1]) or  matrix (non  CRS/SKS)
+              is passed, this function throws exception.
+              
+        NOTE: this function may allocate additional, unnecessary place for  ColIdx
+              and Vals arrays. It is dictated by  performance  reasons  -  on  SKS
+              matrices it is faster  to  allocate  space  at  the  beginning  with
+              some "extra"-space, than performing two passes over matrix  -  first
+              time to calculate exact space required for data, second  time  -  to
+              store data itself.
+
+          -- ALGLIB PROJECT --
+             Copyright 10.12.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsegetcompressedrow(sparsematrix s,
+            int i,
+            ref int[] colidx,
+            ref double[] vals,
+            ref int nzcnt,
+            alglib.xparams _params)
+        {
+            int k = 0;
+            int k0 = 0;
+            int j = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int i0 = 0;
+            int upperprofile = 0;
+
+            nzcnt = 0;
+
+            alglib.ap.assert(s.matrixtype==1 || s.matrixtype==2, "SparseGetRow: S must be CRS/SKS-based matrix");
+            alglib.ap.assert(i>=0 && i<s.m, "SparseGetRow: I<0 or I>=M");
+            
+            //
+            // Initialize NZCnt
+            //
+            nzcnt = 0;
+            
+            //
+            // CRS matrix - just copy data
+            //
+            if( s.matrixtype==1 )
+            {
+                nzcnt = s.ridx[i+1]-s.ridx[i];
+                apserv.ivectorsetlengthatleast(ref colidx, nzcnt, _params);
+                apserv.rvectorsetlengthatleast(ref vals, nzcnt, _params);
+                k0 = s.ridx[i];
+                for(k=0; k<=nzcnt-1; k++)
+                {
+                    colidx[k] = s.idx[k0+k];
+                    vals[k] = s.vals[k0+k];
+                }
+                return;
+            }
+            
+            //
+            // SKS matrix - a bit more complex sequence
+            //
+            if( s.matrixtype==2 )
+            {
+                alglib.ap.assert(s.n==s.m, "SparseGetCompressedRow: non-square SKS matrices are not supported");
+                
+                //
+                // Allocate enough place for storage
+                //
+                upperprofile = s.uidx[s.n];
+                apserv.ivectorsetlengthatleast(ref colidx, s.didx[i]+1+upperprofile, _params);
+                apserv.rvectorsetlengthatleast(ref vals, s.didx[i]+1+upperprofile, _params);
+                
+                //
+                // Copy subdiagonal and diagonal parts
+                //
+                j0 = i-s.didx[i];
+                i0 = -j0+s.ridx[i];
+                for(j=j0; j<=i; j++)
+                {
+                    colidx[nzcnt] = j;
+                    vals[nzcnt] = s.vals[j+i0];
+                    nzcnt = nzcnt+1;
+                }
+                
+                //
+                // Copy superdiagonal part
+                //
+                j0 = i+1;
+                j1 = Math.Min(s.n-1, i+upperprofile);
+                for(j=j0; j<=j1; j++)
+                {
+                    if( j-i<=s.uidx[j] )
+                    {
+                        colidx[nzcnt] = j;
+                        vals[nzcnt] = s.vals[s.ridx[j+1]-(j-i)];
+                        nzcnt = nzcnt+1;
+                    }
+                }
+                return;
+            }
+        }
+
+
+        /*************************************************************************
+        This function performs efficient in-place  transpose  of  SKS  matrix.  No
+        additional memory is allocated during transposition.
+
+        This function supports only skyline storage format (SKS).
+
+        INPUT PARAMETERS
+            S       -   sparse matrix in SKS format.
+
+        OUTPUT PARAMETERS
+            S           -   sparse matrix, transposed.
+
+          -- ALGLIB PROJECT --
+             Copyright 16.01.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsetransposesks(sparsematrix s,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int d = 0;
+            int u = 0;
+            int i = 0;
+            int k = 0;
+            int t0 = 0;
+            int t1 = 0;
+            double v = 0;
+
+            alglib.ap.assert(s.matrixtype==2, "SparseTransposeSKS: only SKS matrices are supported");
+            alglib.ap.assert(s.m==s.n, "SparseTransposeSKS: non-square SKS matrices are not supported");
+            n = s.n;
+            for(i=1; i<=n-1; i++)
+            {
+                d = s.didx[i];
+                u = s.uidx[i];
+                k = s.uidx[i];
+                s.uidx[i] = s.didx[i];
+                s.didx[i] = k;
+                if( d==u )
+                {
+                    
+                    //
+                    // Upper skyline height equal to lower skyline height,
+                    // simple exchange is needed for transposition
+                    //
+                    t0 = s.ridx[i];
+                    for(k=0; k<=d-1; k++)
+                    {
+                        v = s.vals[t0+k];
+                        s.vals[t0+k] = s.vals[t0+d+1+k];
+                        s.vals[t0+d+1+k] = v;
+                    }
+                }
+                if( d>u )
+                {
+                    
+                    //
+                    // Upper skyline height is less than lower skyline height.
+                    //
+                    // Transposition becomes a bit tricky: we have to rearrange
+                    // "L0 L1 D U" to "U D L0 L1", where |L0|=|U|=u, |L1|=d-u.
+                    //
+                    // In order to do this we perform a sequence of swaps and
+                    // in-place reversals:
+                    // * swap(L0,U)         =>  "U   L1  D   L0"
+                    // * reverse("L1 D L0") =>  "U   L0~ D   L1~" (where X~ is a reverse of X)
+                    // * reverse("L0~ D")   =>  "U   D   L0  L1~"
+                    // * reverse("L1")      =>  "U   D   L0  L1"
+                    //
+                    t0 = s.ridx[i];
+                    t1 = s.ridx[i]+d+1;
+                    for(k=0; k<=u-1; k++)
+                    {
+                        v = s.vals[t0+k];
+                        s.vals[t0+k] = s.vals[t1+k];
+                        s.vals[t1+k] = v;
+                    }
+                    t0 = s.ridx[i]+u;
+                    t1 = s.ridx[i+1]-1;
+                    while( t1>t0 )
+                    {
+                        v = s.vals[t0];
+                        s.vals[t0] = s.vals[t1];
+                        s.vals[t1] = v;
+                        t0 = t0+1;
+                        t1 = t1-1;
+                    }
+                    t0 = s.ridx[i]+u;
+                    t1 = s.ridx[i]+u+u;
+                    while( t1>t0 )
+                    {
+                        v = s.vals[t0];
+                        s.vals[t0] = s.vals[t1];
+                        s.vals[t1] = v;
+                        t0 = t0+1;
+                        t1 = t1-1;
+                    }
+                    t0 = s.ridx[i+1]-(d-u);
+                    t1 = s.ridx[i+1]-1;
+                    while( t1>t0 )
+                    {
+                        v = s.vals[t0];
+                        s.vals[t0] = s.vals[t1];
+                        s.vals[t1] = v;
+                        t0 = t0+1;
+                        t1 = t1-1;
+                    }
+                }
+                if( d<u )
+                {
+                    
+                    //
+                    // Upper skyline height is greater than lower skyline height.
+                    //
+                    // Transposition becomes a bit tricky: we have to rearrange
+                    // "L D U0 U1" to "U0 U1 D L", where |U1|=|L|=d, |U0|=u-d.
+                    //
+                    // In order to do this we perform a sequence of swaps and
+                    // in-place reversals:
+                    // * swap(L,U1)         =>  "U1  D   U0  L"
+                    // * reverse("U1 D U0") =>  "U0~ D   U1~ L" (where X~ is a reverse of X)
+                    // * reverse("U0~")     =>  "U0  D   U1~ L"
+                    // * reverse("D U1~")   =>  "U0  U1  D   L"
+                    //
+                    t0 = s.ridx[i];
+                    t1 = s.ridx[i+1]-d;
+                    for(k=0; k<=d-1; k++)
+                    {
+                        v = s.vals[t0+k];
+                        s.vals[t0+k] = s.vals[t1+k];
+                        s.vals[t1+k] = v;
+                    }
+                    t0 = s.ridx[i];
+                    t1 = s.ridx[i]+u;
+                    while( t1>t0 )
+                    {
+                        v = s.vals[t0];
+                        s.vals[t0] = s.vals[t1];
+                        s.vals[t1] = v;
+                        t0 = t0+1;
+                        t1 = t1-1;
+                    }
+                    t0 = s.ridx[i];
+                    t1 = s.ridx[i]+u-d-1;
+                    while( t1>t0 )
+                    {
+                        v = s.vals[t0];
+                        s.vals[t0] = s.vals[t1];
+                        s.vals[t1] = v;
+                        t0 = t0+1;
+                        t1 = t1-1;
+                    }
+                    t0 = s.ridx[i]+u-d;
+                    t1 = s.ridx[i+1]-d-1;
+                    while( t1>t0 )
+                    {
+                        v = s.vals[t0];
+                        s.vals[t0] = s.vals[t1];
+                        s.vals[t1] = v;
+                        t0 = t0+1;
+                        t1 = t1-1;
+                    }
+                }
+            }
+            k = s.uidx[n];
+            s.uidx[n] = s.didx[n];
+            s.didx[n] = k;
+        }
+
+
+        /*************************************************************************
+        This function performs transpose of CRS matrix.
+
+        INPUT PARAMETERS
+            S       -   sparse matrix in CRS format.
+
+        OUTPUT PARAMETERS
+            S           -   sparse matrix, transposed.
+
+        NOTE: internal  temporary  copy  is  allocated   for   the   purposes   of
+              transposition. It is deallocated after transposition.
+
+          -- ALGLIB PROJECT --
+             Copyright 30.01.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsetransposecrs(sparsematrix s,
+            alglib.xparams _params)
+        {
+            double[] oldvals = new double[0];
+            int[] oldidx = new int[0];
+            int[] oldridx = new int[0];
+            int oldn = 0;
+            int oldm = 0;
+            int newn = 0;
+            int newm = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int nonne = 0;
+            int[] counts = new int[0];
+
+            alglib.ap.assert(s.matrixtype==1, "SparseTransposeCRS: only CRS matrices are supported");
+            alglib.ap.swap(ref s.vals, ref oldvals);
+            alglib.ap.swap(ref s.idx, ref oldidx);
+            alglib.ap.swap(ref s.ridx, ref oldridx);
+            oldn = s.n;
+            oldm = s.m;
+            newn = oldm;
+            newm = oldn;
+            
+            //
+            // Update matrix size
+            //
+            s.n = newn;
+            s.m = newm;
+            
+            //
+            // Fill RIdx by number of elements per row:
+            // RIdx[I+1] stores number of elements in I-th row.
+            //
+            // Convert RIdx from row sizes to row offsets.
+            // Set NInitialized
+            //
+            nonne = 0;
+            apserv.ivectorsetlengthatleast(ref s.ridx, newm+1, _params);
+            for(i=0; i<=newm; i++)
+            {
+                s.ridx[i] = 0;
+            }
+            for(i=0; i<=oldm-1; i++)
+            {
+                for(j=oldridx[i]; j<=oldridx[i+1]-1; j++)
+                {
+                    k = oldidx[j]+1;
+                    s.ridx[k] = s.ridx[k]+1;
+                    nonne = nonne+1;
+                }
+            }
+            for(i=0; i<=newm-1; i++)
+            {
+                s.ridx[i+1] = s.ridx[i+1]+s.ridx[i];
+            }
+            s.ninitialized = s.ridx[newm];
+            
+            //
+            // Allocate memory and move elements to Vals/Idx.
+            //
+            counts = new int[newm];
+            for(i=0; i<=newm-1; i++)
+            {
+                counts[i] = 0;
+            }
+            apserv.rvectorsetlengthatleast(ref s.vals, nonne, _params);
+            apserv.ivectorsetlengthatleast(ref s.idx, nonne, _params);
+            for(i=0; i<=oldm-1; i++)
+            {
+                for(j=oldridx[i]; j<=oldridx[i+1]-1; j++)
+                {
+                    k = oldidx[j];
+                    k = s.ridx[k]+counts[k];
+                    s.idx[k] = i;
+                    s.vals[k] = oldvals[j];
+                    k = oldidx[j];
+                    counts[k] = counts[k]+1;
+                }
+            }
+            
+            //
+            // Initialization 'S.UIdx' and 'S.DIdx'
+            //
+            sparseinitduidx(s, _params);
+        }
+
+
+        /*************************************************************************
+        This function performs copying with transposition of CRS matrix.
+
+        INPUT PARAMETERS
+            S0      -   sparse matrix in CRS format.
+
+        OUTPUT PARAMETERS
+            S1      -   sparse matrix, transposed
+
+          -- ALGLIB PROJECT --
+             Copyright 23.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecopytransposecrs(sparsematrix s0,
+            sparsematrix s1,
+            alglib.xparams _params)
+        {
+            sparsecopytransposecrsbuf(s0, s1, _params);
+        }
+
+
+        /*************************************************************************
+        This function performs copying with transposition of CRS matrix  (buffered
+        version which reuses memory already allocated by  the  target as  much  as
+        possible).
+
+        INPUT PARAMETERS
+            S0      -   sparse matrix in CRS format.
+
+        OUTPUT PARAMETERS
+            S1      -   sparse matrix, transposed; previously allocated memory  is
+                        reused if possible.
+
+          -- ALGLIB PROJECT --
+             Copyright 23.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecopytransposecrsbuf(sparsematrix s0,
+            sparsematrix s1,
+            alglib.xparams _params)
+        {
+            int oldn = 0;
+            int oldm = 0;
+            int newn = 0;
+            int newm = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int nonne = 0;
+            int[] counts = new int[0];
+
+            alglib.ap.assert(s0.matrixtype==1, "SparseCopyTransposeCRSBuf: only CRS matrices are supported");
+            oldn = s0.n;
+            oldm = s0.m;
+            newn = oldm;
+            newm = oldn;
+            
+            //
+            // Update matrix size
+            //
+            s1.matrixtype = 1;
+            s1.n = newn;
+            s1.m = newm;
+            
+            //
+            // Fill RIdx by number of elements per row:
+            // RIdx[I+1] stores number of elements in I-th row.
+            //
+            // Convert RIdx from row sizes to row offsets.
+            // Set NInitialized
+            //
+            nonne = 0;
+            apserv.ivectorsetlengthatleast(ref s1.ridx, newm+1, _params);
+            for(i=0; i<=newm; i++)
+            {
+                s1.ridx[i] = 0;
+            }
+            for(i=0; i<=oldm-1; i++)
+            {
+                for(j=s0.ridx[i]; j<=s0.ridx[i+1]-1; j++)
+                {
+                    k = s0.idx[j]+1;
+                    s1.ridx[k] = s1.ridx[k]+1;
+                    nonne = nonne+1;
+                }
+            }
+            for(i=0; i<=newm-1; i++)
+            {
+                s1.ridx[i+1] = s1.ridx[i+1]+s1.ridx[i];
+            }
+            s1.ninitialized = s1.ridx[newm];
+            
+            //
+            // Allocate memory and move elements to Vals/Idx.
+            //
+            counts = new int[newm];
+            for(i=0; i<=newm-1; i++)
+            {
+                counts[i] = 0;
+            }
+            apserv.rvectorsetlengthatleast(ref s1.vals, nonne, _params);
+            apserv.ivectorsetlengthatleast(ref s1.idx, nonne, _params);
+            for(i=0; i<=oldm-1; i++)
+            {
+                for(j=s0.ridx[i]; j<=s0.ridx[i+1]-1; j++)
+                {
+                    k = s0.idx[j];
+                    k = s1.ridx[k]+counts[k];
+                    s1.idx[k] = i;
+                    s1.vals[k] = s0.vals[j];
+                    k = s0.idx[j];
+                    counts[k] = counts[k]+1;
+                }
+            }
+            
+            //
+            // Initialization 'S.UIdx' and 'S.DIdx'
+            //
+            sparseinitduidx(s1, _params);
+        }
+
+
+        /*************************************************************************
+        This  function  performs  in-place  conversion  to  desired sparse storage
+        format.
+
+        INPUT PARAMETERS
+            S0      -   sparse matrix in any format.
+            Fmt     -   desired storage format  of  the  output,  as  returned  by
+                        SparseGetMatrixType() function:
+                        * 0 for hash-based storage
+                        * 1 for CRS
+                        * 2 for SKS
+
+        OUTPUT PARAMETERS
+            S0          -   sparse matrix in requested format.
+            
+        NOTE: in-place conversion wastes a lot of memory which is  used  to  store
+              temporaries.  If  you  perform  a  lot  of  repeated conversions, we
+              recommend to use out-of-place buffered  conversion  functions,  like
+              SparseCopyToBuf(), which can reuse already allocated memory.
+
+          -- ALGLIB PROJECT --
+             Copyright 16.01.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparseconvertto(sparsematrix s0,
+            int fmt,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert((fmt==0 || fmt==1) || fmt==2, "SparseConvertTo: invalid fmt parameter");
+            if( fmt==0 )
+            {
+                sparseconverttohash(s0, _params);
+                return;
+            }
+            if( fmt==1 )
+            {
+                sparseconverttocrs(s0, _params);
+                return;
+            }
+            if( fmt==2 )
+            {
+                sparseconverttosks(s0, _params);
+                return;
+            }
+            alglib.ap.assert(false, "SparseConvertTo: invalid matrix type");
+        }
+
+
+        /*************************************************************************
+        This  function  performs out-of-place conversion to desired sparse storage
+        format. S0 is copied to S1 and converted on-the-fly. Memory  allocated  in
+        S1 is reused to maximum extent possible.
+
+        INPUT PARAMETERS
+            S0      -   sparse matrix in any format.
+            Fmt     -   desired storage format  of  the  output,  as  returned  by
+                        SparseGetMatrixType() function:
+                        * 0 for hash-based storage
+                        * 1 for CRS
+                        * 2 for SKS
+
+        OUTPUT PARAMETERS
+            S1          -   sparse matrix in requested format.
+
+          -- ALGLIB PROJECT --
+             Copyright 16.01.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecopytobuf(sparsematrix s0,
+            int fmt,
+            sparsematrix s1,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert((fmt==0 || fmt==1) || fmt==2, "SparseCopyToBuf: invalid fmt parameter");
+            if( fmt==0 )
+            {
+                sparsecopytohashbuf(s0, s1, _params);
+                return;
+            }
+            if( fmt==1 )
+            {
+                sparsecopytocrsbuf(s0, s1, _params);
+                return;
+            }
+            if( fmt==2 )
+            {
+                sparsecopytosksbuf(s0, s1, _params);
+                return;
+            }
+            alglib.ap.assert(false, "SparseCopyToBuf: invalid matrix type");
+        }
+
+
+        /*************************************************************************
+        This function performs in-place conversion to Hash table storage.
+
+        INPUT PARAMETERS
+            S           -   sparse matrix in CRS format.
+
+        OUTPUT PARAMETERS
+            S           -   sparse matrix in Hash table format.
+
+        NOTE: this  function  has   no  effect  when  called with matrix which  is
+              already in Hash table mode.
+
+        NOTE: in-place conversion involves allocation of temporary arrays. If  you
+              perform a lot of repeated in- place  conversions,  it  may  lead  to
+              memory fragmentation. Consider using out-of-place SparseCopyToHashBuf()
+              function in this case.
+            
+          -- ALGLIB PROJECT --
+             Copyright 20.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparseconverttohash(sparsematrix s,
+            alglib.xparams _params)
+        {
+            int[] tidx = new int[0];
+            int[] tridx = new int[0];
+            int[] tdidx = new int[0];
+            int[] tuidx = new int[0];
+            double[] tvals = new double[0];
+            int n = 0;
+            int m = 0;
+            int offs0 = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+
+            alglib.ap.assert((s.matrixtype==0 || s.matrixtype==1) || s.matrixtype==2, "SparseConvertToHash: invalid matrix type");
+            if( s.matrixtype==0 )
+            {
+                
+                //
+                // Already in Hash mode
+                //
+                return;
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // From CRS to Hash
+                //
+                s.matrixtype = 0;
+                m = s.m;
+                n = s.n;
+                alglib.ap.swap(ref s.idx, ref tidx);
+                alglib.ap.swap(ref s.ridx, ref tridx);
+                alglib.ap.swap(ref s.vals, ref tvals);
+                sparsecreatebuf(m, n, tridx[m], s, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    for(j=tridx[i]; j<=tridx[i+1]-1; j++)
+                    {
+                        sparseset(s, i, tidx[j], tvals[j], _params);
+                    }
+                }
+                return;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // From SKS to Hash
+                //
+                s.matrixtype = 0;
+                m = s.m;
+                n = s.n;
+                alglib.ap.swap(ref s.ridx, ref tridx);
+                alglib.ap.swap(ref s.didx, ref tdidx);
+                alglib.ap.swap(ref s.uidx, ref tuidx);
+                alglib.ap.swap(ref s.vals, ref tvals);
+                sparsecreatebuf(m, n, tridx[m], s, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    
+                    //
+                    // copy subdiagonal and diagonal parts of I-th block
+                    //
+                    offs0 = tridx[i];
+                    k = tdidx[i]+1;
+                    for(j=0; j<=k-1; j++)
+                    {
+                        sparseset(s, i, i-tdidx[i]+j, tvals[offs0+j], _params);
+                    }
+                    
+                    //
+                    // Copy superdiagonal part of I-th block
+                    //
+                    offs0 = tridx[i]+tdidx[i]+1;
+                    k = tuidx[i];
+                    for(j=0; j<=k-1; j++)
+                    {
+                        sparseset(s, i-k+j, i, tvals[offs0+j], _params);
+                    }
+                }
+                return;
+            }
+            alglib.ap.assert(false, "SparseConvertToHash: invalid matrix type");
+        }
+
+
+        /*************************************************************************
+        This  function  performs  out-of-place  conversion  to  Hash table storage
+        format. S0 is copied to S1 and converted on-the-fly.
+
+        INPUT PARAMETERS
+            S0          -   sparse matrix in any format.
+
+        OUTPUT PARAMETERS
+            S1          -   sparse matrix in Hash table format.
+
+        NOTE: if S0 is stored as Hash-table, it is just copied without conversion.
+
+        NOTE: this function de-allocates memory  occupied  by  S1 before  starting
+              conversion. If you perform a  lot  of  repeated  conversions, it may
+              lead to memory fragmentation. In this case we recommend you  to  use
+              SparseCopyToHashBuf() function which re-uses memory in S1 as much as
+              possible.
+
+          -- ALGLIB PROJECT --
+             Copyright 20.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecopytohash(sparsematrix s0,
+            sparsematrix s1,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert((s0.matrixtype==0 || s0.matrixtype==1) || s0.matrixtype==2, "SparseCopyToHash: invalid matrix type");
+            sparsecopytohashbuf(s0, s1, _params);
+        }
+
+
+        /*************************************************************************
+        This  function  performs  out-of-place  conversion  to  Hash table storage
+        format. S0 is copied to S1 and converted on-the-fly. Memory  allocated  in
+        S1 is reused to maximum extent possible.
+
+        INPUT PARAMETERS
+            S0          -   sparse matrix in any format.
+
+        OUTPUT PARAMETERS
+            S1          -   sparse matrix in Hash table format.
+
+        NOTE: if S0 is stored as Hash-table, it is just copied without conversion.
+
+          -- ALGLIB PROJECT --
+             Copyright 20.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecopytohashbuf(sparsematrix s0,
+            sparsematrix s1,
+            alglib.xparams _params)
+        {
+            double val = 0;
+            int t0 = 0;
+            int t1 = 0;
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert((s0.matrixtype==0 || s0.matrixtype==1) || s0.matrixtype==2, "SparseCopyToHashBuf: invalid matrix type");
+            if( s0.matrixtype==0 )
+            {
+                
+                //
+                // Already hash, just copy
+                //
+                sparsecopybuf(s0, s1, _params);
+                return;
+            }
+            if( s0.matrixtype==1 )
+            {
+                
+                //
+                // CRS storage
+                //
+                t0 = 0;
+                t1 = 0;
+                sparsecreatebuf(s0.m, s0.n, s0.ridx[s0.m], s1, _params);
+                while( sparseenumerate(s0, ref t0, ref t1, ref i, ref j, ref val, _params) )
+                {
+                    sparseset(s1, i, j, val, _params);
+                }
+                return;
+            }
+            if( s0.matrixtype==2 )
+            {
+                
+                //
+                // SKS storage
+                //
+                t0 = 0;
+                t1 = 0;
+                sparsecreatebuf(s0.m, s0.n, s0.ridx[s0.m], s1, _params);
+                while( sparseenumerate(s0, ref t0, ref t1, ref i, ref j, ref val, _params) )
+                {
+                    sparseset(s1, i, j, val, _params);
+                }
+                return;
+            }
+            alglib.ap.assert(false, "SparseCopyToHashBuf: invalid matrix type");
+        }
+
+
+        /*************************************************************************
+        This function converts matrix to CRS format.
+
+        Some  algorithms  (linear  algebra ones, for example) require matrices in
+        CRS format. This function allows to perform in-place conversion.
+
+        INPUT PARAMETERS
+            S           -   sparse M*N matrix in any format
+
+        OUTPUT PARAMETERS
+            S           -   matrix in CRS format
+            
+        NOTE: this   function  has  no  effect  when  called with matrix which is
+              already in CRS mode.
+              
+        NOTE: this function allocates temporary memory to store a   copy  of  the
+              matrix. If you perform a lot of repeated conversions, we  recommend
+              you  to  use  SparseCopyToCRSBuf()  function,   which   can   reuse
+              previously allocated memory.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparseconverttocrs(sparsematrix s,
+            alglib.xparams _params)
+        {
+            int m = 0;
+            int i = 0;
+            int j = 0;
+            double[] tvals = new double[0];
+            int[] tidx = new int[0];
+            int[] temp = new int[0];
+            int[] tridx = new int[0];
+            int nonne = 0;
+            int k = 0;
+            int offs0 = 0;
+            int offs1 = 0;
+
+            m = s.m;
+            if( s.matrixtype==0 )
+            {
+                
+                //
+                // From Hash-table to CRS.
+                // First, create local copy of the hash table.
+                //
+                s.matrixtype = 1;
+                k = s.tablesize;
+                alglib.ap.swap(ref s.vals, ref tvals);
+                alglib.ap.swap(ref s.idx, ref tidx);
+                
+                //
+                // Fill RIdx by number of elements per row:
+                // RIdx[I+1] stores number of elements in I-th row.
+                //
+                // Convert RIdx from row sizes to row offsets.
+                // Set NInitialized
+                //
+                nonne = 0;
+                apserv.ivectorsetlengthatleast(ref s.ridx, s.m+1, _params);
+                for(i=0; i<=s.m; i++)
+                {
+                    s.ridx[i] = 0;
+                }
+                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;
+                    }
+                }
+                for(i=0; i<=s.m-1; i++)
+                {
+                    s.ridx[i+1] = s.ridx[i+1]+s.ridx[i];
+                }
+                s.ninitialized = s.ridx[s.m];
+                
+                //
+                // Allocate memory and move elements to Vals/Idx.
+                // Initially, elements are sorted by rows, but unsorted within row.
+                // After initial insertion we sort elements within row.
+                //
+                temp = new int[s.m];
+                for(i=0; i<=s.m-1; i++)
+                {
+                    temp[i] = 0;
+                }
+                apserv.rvectorsetlengthatleast(ref s.vals, nonne, _params);
+                apserv.ivectorsetlengthatleast(ref s.idx, nonne, _params);
+                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;
+                    }
+                }
+                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], _params);
+                }
+                
+                //
+                // Initialization 'S.UIdx' and 'S.DIdx'
+                //
+                sparseinitduidx(s, _params);
+                return;
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // Already CRS
+                //
+                return;
+            }
+            if( s.matrixtype==2 )
+            {
+                alglib.ap.assert(s.m==s.n, "SparseConvertToCRS: non-square SKS matrices are not supported");
+                
+                //
+                // From SKS to CRS.
+                //
+                // First, create local copy of the SKS matrix (Vals,
+                // Idx, RIdx are stored; DIdx/UIdx for some time are
+                // left in the SparseMatrix structure).
+                //
+                s.matrixtype = 1;
+                alglib.ap.swap(ref s.vals, ref tvals);
+                alglib.ap.swap(ref s.idx, ref tidx);
+                alglib.ap.swap(ref s.ridx, ref tridx);
+                
+                //
+                // Fill RIdx by number of elements per row:
+                // RIdx[I+1] stores number of elements in I-th row.
+                //
+                // Convert RIdx from row sizes to row offsets.
+                // Set NInitialized
+                //
+                apserv.ivectorsetlengthatleast(ref s.ridx, m+1, _params);
+                s.ridx[0] = 0;
+                for(i=1; i<=m; i++)
+                {
+                    s.ridx[i] = 1;
+                }
+                nonne = 0;
+                for(i=0; i<=m-1; i++)
+                {
+                    s.ridx[i+1] = s.didx[i]+s.ridx[i+1];
+                    for(j=i-s.uidx[i]; j<=i-1; j++)
+                    {
+                        s.ridx[j+1] = s.ridx[j+1]+1;
+                    }
+                    nonne = nonne+s.didx[i]+1+s.uidx[i];
+                }
+                for(i=0; i<=s.m-1; i++)
+                {
+                    s.ridx[i+1] = s.ridx[i+1]+s.ridx[i];
+                }
+                s.ninitialized = s.ridx[s.m];
+                
+                //
+                // Allocate memory and move elements to Vals/Idx.
+                // Initially, elements are sorted by rows, and are sorted within row too.
+                // No additional post-sorting is required.
+                //
+                temp = new int[m];
+                for(i=0; i<=m-1; i++)
+                {
+                    temp[i] = 0;
+                }
+                apserv.rvectorsetlengthatleast(ref s.vals, nonne, _params);
+                apserv.ivectorsetlengthatleast(ref s.idx, nonne, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    
+                    //
+                    // copy subdiagonal and diagonal parts of I-th block
+                    //
+                    offs0 = tridx[i];
+                    offs1 = s.ridx[i]+temp[i];
+                    k = s.didx[i]+1;
+                    for(j=0; j<=k-1; j++)
+                    {
+                        s.vals[offs1+j] = tvals[offs0+j];
+                        s.idx[offs1+j] = i-s.didx[i]+j;
+                    }
+                    temp[i] = temp[i]+s.didx[i]+1;
+                    
+                    //
+                    // Copy superdiagonal part of I-th block
+                    //
+                    offs0 = tridx[i]+s.didx[i]+1;
+                    k = s.uidx[i];
+                    for(j=0; j<=k-1; j++)
+                    {
+                        offs1 = s.ridx[i-k+j]+temp[i-k+j];
+                        s.vals[offs1] = tvals[offs0+j];
+                        s.idx[offs1] = i;
+                        temp[i-k+j] = temp[i-k+j]+1;
+                    }
+                }
+                
+                //
+                // Initialization 'S.UIdx' and 'S.DIdx'
+                //
+                sparseinitduidx(s, _params);
+                return;
+            }
+            alglib.ap.assert(false, "SparseConvertToCRS: invalid matrix type");
+        }
+
+
+        /*************************************************************************
+        This  function  performs  out-of-place  conversion  to  CRS format.  S0 is
+        copied to S1 and converted on-the-fly.
+
+        INPUT PARAMETERS
+            S0          -   sparse matrix in any format.
+
+        OUTPUT PARAMETERS
+            S1          -   sparse matrix in CRS format.
+            
+        NOTE: if S0 is stored as CRS, it is just copied without conversion.
+
+        NOTE: this function de-allocates memory occupied by S1 before starting CRS
+              conversion. If you perform a lot of repeated CRS conversions, it may
+              lead to memory fragmentation. In this case we recommend you  to  use
+              SparseCopyToCRSBuf() function which re-uses memory in S1 as much  as
+              possible.
+
+          -- ALGLIB PROJECT --
+             Copyright 20.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecopytocrs(sparsematrix s0,
+            sparsematrix s1,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert((s0.matrixtype==0 || s0.matrixtype==1) || s0.matrixtype==2, "SparseCopyToCRS: invalid matrix type");
+            sparsecopytocrsbuf(s0, s1, _params);
+        }
+
+
+        /*************************************************************************
+        This  function  performs  out-of-place  conversion  to  CRS format.  S0 is
+        copied to S1 and converted on-the-fly. Memory allocated in S1 is reused to
+        maximum extent possible.
+
+        INPUT PARAMETERS
+            S0          -   sparse matrix in any format.
+            S1          -   matrix which may contain some pre-allocated memory, or
+                            can be just uninitialized structure.
+
+        OUTPUT PARAMETERS
+            S1          -   sparse matrix in CRS format.
+            
+        NOTE: if S0 is stored as CRS, it is just copied without conversion.
+
+          -- ALGLIB PROJECT --
+             Copyright 20.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecopytocrsbuf(sparsematrix s0,
+            sparsematrix s1,
+            alglib.xparams _params)
+        {
+            int[] temp = new int[0];
+            int nonne = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int offs0 = 0;
+            int offs1 = 0;
+            int m = 0;
+
+            alglib.ap.assert((s0.matrixtype==0 || s0.matrixtype==1) || s0.matrixtype==2, "SparseCopyToCRSBuf: invalid matrix type");
+            m = s0.m;
+            if( s0.matrixtype==0 )
+            {
+                
+                //
+                // Convert from hash-table to CRS
+                // Done like ConvertToCRS function
+                //
+                s1.matrixtype = 1;
+                s1.m = s0.m;
+                s1.n = s0.n;
+                s1.nfree = s0.nfree;
+                nonne = 0;
+                k = s0.tablesize;
+                apserv.ivectorsetlengthatleast(ref s1.ridx, s1.m+1, _params);
+                for(i=0; i<=s1.m; i++)
+                {
+                    s1.ridx[i] = 0;
+                }
+                temp = new int[s1.m];
+                for(i=0; i<=s1.m-1; i++)
+                {
+                    temp[i] = 0;
+                }
+                
+                //
+                // Number of elements per row
+                //
+                for(i=0; i<=k-1; i++)
+                {
+                    if( s0.idx[2*i]>=0 )
+                    {
+                        s1.ridx[s0.idx[2*i]+1] = s1.ridx[s0.idx[2*i]+1]+1;
+                        nonne = nonne+1;
+                    }
+                }
+                
+                //
+                // Fill RIdx (offsets of rows)
+                //
+                for(i=0; i<=s1.m-1; i++)
+                {
+                    s1.ridx[i+1] = s1.ridx[i+1]+s1.ridx[i];
+                }
+                
+                //
+                // Allocate memory
+                //
+                apserv.rvectorsetlengthatleast(ref s1.vals, nonne, _params);
+                apserv.ivectorsetlengthatleast(ref s1.idx, nonne, _params);
+                for(i=0; i<=k-1; i++)
+                {
+                    if( s0.idx[2*i]>=0 )
+                    {
+                        s1.vals[s1.ridx[s0.idx[2*i]]+temp[s0.idx[2*i]]] = s0.vals[i];
+                        s1.idx[s1.ridx[s0.idx[2*i]]+temp[s0.idx[2*i]]] = s0.idx[2*i+1];
+                        temp[s0.idx[2*i]] = temp[s0.idx[2*i]]+1;
+                    }
+                }
+                
+                //
+                // Set NInitialized
+                //
+                s1.ninitialized = s1.ridx[s1.m];
+                
+                //
+                // Sorting of elements
+                //
+                for(i=0; i<=s1.m-1; i++)
+                {
+                    tsort.tagsortmiddleir(ref s1.idx, ref s1.vals, s1.ridx[i], s1.ridx[i+1]-s1.ridx[i], _params);
+                }
+                
+                //
+                // Initialization 'S.UIdx' and 'S.DIdx'
+                //
+                sparseinitduidx(s1, _params);
+                return;
+            }
+            if( s0.matrixtype==1 )
+            {
+                
+                //
+                // Already CRS, just copy
+                //
+                sparsecopybuf(s0, s1, _params);
+                return;
+            }
+            if( s0.matrixtype==2 )
+            {
+                alglib.ap.assert(s0.m==s0.n, "SparseCopyToCRS: non-square SKS matrices are not supported");
+                
+                //
+                // From SKS to CRS.
+                //
+                s1.m = s0.m;
+                s1.n = s0.n;
+                s1.matrixtype = 1;
+                
+                //
+                // Fill RIdx by number of elements per row:
+                // RIdx[I+1] stores number of elements in I-th row.
+                //
+                // Convert RIdx from row sizes to row offsets.
+                // Set NInitialized
+                //
+                apserv.ivectorsetlengthatleast(ref s1.ridx, m+1, _params);
+                s1.ridx[0] = 0;
+                for(i=1; i<=m; i++)
+                {
+                    s1.ridx[i] = 1;
+                }
+                nonne = 0;
+                for(i=0; i<=m-1; i++)
+                {
+                    s1.ridx[i+1] = s0.didx[i]+s1.ridx[i+1];
+                    for(j=i-s0.uidx[i]; j<=i-1; j++)
+                    {
+                        s1.ridx[j+1] = s1.ridx[j+1]+1;
+                    }
+                    nonne = nonne+s0.didx[i]+1+s0.uidx[i];
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    s1.ridx[i+1] = s1.ridx[i+1]+s1.ridx[i];
+                }
+                s1.ninitialized = s1.ridx[m];
+                
+                //
+                // Allocate memory and move elements to Vals/Idx.
+                // Initially, elements are sorted by rows, and are sorted within row too.
+                // No additional post-sorting is required.
+                //
+                temp = new int[m];
+                for(i=0; i<=m-1; i++)
+                {
+                    temp[i] = 0;
+                }
+                apserv.rvectorsetlengthatleast(ref s1.vals, nonne, _params);
+                apserv.ivectorsetlengthatleast(ref s1.idx, nonne, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    
+                    //
+                    // copy subdiagonal and diagonal parts of I-th block
+                    //
+                    offs0 = s0.ridx[i];
+                    offs1 = s1.ridx[i]+temp[i];
+                    k = s0.didx[i]+1;
+                    for(j=0; j<=k-1; j++)
+                    {
+                        s1.vals[offs1+j] = s0.vals[offs0+j];
+                        s1.idx[offs1+j] = i-s0.didx[i]+j;
+                    }
+                    temp[i] = temp[i]+s0.didx[i]+1;
+                    
+                    //
+                    // Copy superdiagonal part of I-th block
+                    //
+                    offs0 = s0.ridx[i]+s0.didx[i]+1;
+                    k = s0.uidx[i];
+                    for(j=0; j<=k-1; j++)
+                    {
+                        offs1 = s1.ridx[i-k+j]+temp[i-k+j];
+                        s1.vals[offs1] = s0.vals[offs0+j];
+                        s1.idx[offs1] = i;
+                        temp[i-k+j] = temp[i-k+j]+1;
+                    }
+                }
+                
+                //
+                // Initialization 'S.UIdx' and 'S.DIdx'
+                //
+                sparseinitduidx(s1, _params);
+                return;
+            }
+            alglib.ap.assert(false, "SparseCopyToCRSBuf: unexpected matrix type");
+        }
+
+
+        /*************************************************************************
+        This function performs in-place conversion to SKS format.
+
+        INPUT PARAMETERS
+            S           -   sparse matrix in any format.
+
+        OUTPUT PARAMETERS
+            S           -   sparse matrix in SKS format.
+
+        NOTE: this  function  has   no  effect  when  called with matrix which  is
+              already in SKS mode.
+
+        NOTE: in-place conversion involves allocation of temporary arrays. If  you
+              perform a lot of repeated in- place  conversions,  it  may  lead  to
+              memory fragmentation. Consider using out-of-place SparseCopyToSKSBuf()
+              function in this case.
+            
+          -- ALGLIB PROJECT --
+             Copyright 15.01.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparseconverttosks(sparsematrix s,
+            alglib.xparams _params)
+        {
+            int[] tridx = new int[0];
+            int[] tdidx = new int[0];
+            int[] tuidx = new int[0];
+            double[] tvals = new double[0];
+            int n = 0;
+            int t0 = 0;
+            int t1 = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+
+            alglib.ap.assert((s.matrixtype==0 || s.matrixtype==1) || s.matrixtype==2, "SparseConvertToSKS: invalid matrix type");
+            alglib.ap.assert(s.m==s.n, "SparseConvertToSKS: rectangular matrices are not supported");
+            n = s.n;
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // Already in SKS mode
+                //
+                return;
+            }
+            
+            //
+            // Generate internal copy of SKS matrix
+            //
+            apserv.ivectorsetlengthatleast(ref tdidx, n+1, _params);
+            apserv.ivectorsetlengthatleast(ref tuidx, n+1, _params);
+            for(i=0; i<=n; i++)
+            {
+                tdidx[i] = 0;
+                tuidx[i] = 0;
+            }
+            t0 = 0;
+            t1 = 0;
+            while( sparseenumerate(s, ref t0, ref t1, ref i, ref j, ref v, _params) )
+            {
+                if( j<i )
+                {
+                    tdidx[i] = Math.Max(tdidx[i], i-j);
+                }
+                else
+                {
+                    tuidx[j] = Math.Max(tuidx[j], j-i);
+                }
+            }
+            apserv.ivectorsetlengthatleast(ref tridx, n+1, _params);
+            tridx[0] = 0;
+            for(i=1; i<=n; i++)
+            {
+                tridx[i] = tridx[i-1]+tdidx[i-1]+1+tuidx[i-1];
+            }
+            apserv.rvectorsetlengthatleast(ref tvals, tridx[n], _params);
+            k = tridx[n];
+            for(i=0; i<=k-1; i++)
+            {
+                tvals[i] = 0.0;
+            }
+            t0 = 0;
+            t1 = 0;
+            while( sparseenumerate(s, ref t0, ref t1, ref i, ref j, ref v, _params) )
+            {
+                if( j<=i )
+                {
+                    tvals[tridx[i]+tdidx[i]-(i-j)] = v;
+                }
+                else
+                {
+                    tvals[tridx[j+1]-(j-i)] = v;
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                tdidx[n] = Math.Max(tdidx[n], tdidx[i]);
+                tuidx[n] = Math.Max(tuidx[n], tuidx[i]);
+            }
+            s.matrixtype = 2;
+            s.ninitialized = 0;
+            s.nfree = 0;
+            s.m = n;
+            s.n = n;
+            alglib.ap.swap(ref s.didx, ref tdidx);
+            alglib.ap.swap(ref s.uidx, ref tuidx);
+            alglib.ap.swap(ref s.ridx, ref tridx);
+            alglib.ap.swap(ref s.vals, ref tvals);
+        }
+
+
+        /*************************************************************************
+        This  function  performs  out-of-place  conversion  to SKS storage format.
+        S0 is copied to S1 and converted on-the-fly.
+
+        INPUT PARAMETERS
+            S0          -   sparse matrix in any format.
+
+        OUTPUT PARAMETERS
+            S1          -   sparse matrix in SKS format.
+
+        NOTE: if S0 is stored as SKS, it is just copied without conversion.
+
+        NOTE: this function de-allocates memory  occupied  by  S1 before  starting
+              conversion. If you perform a  lot  of  repeated  conversions, it may
+              lead to memory fragmentation. In this case we recommend you  to  use
+              SparseCopyToSKSBuf() function which re-uses memory in S1 as much  as
+              possible.
+
+          -- ALGLIB PROJECT --
+             Copyright 20.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecopytosks(sparsematrix s0,
+            sparsematrix s1,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert((s0.matrixtype==0 || s0.matrixtype==1) || s0.matrixtype==2, "SparseCopyToSKS: invalid matrix type");
+            sparsecopytosksbuf(s0, s1, _params);
+        }
+
+
+        /*************************************************************************
+        This  function  performs  out-of-place  conversion  to SKS format.  S0  is
+        copied to S1 and converted on-the-fly. Memory  allocated  in S1 is  reused
+        to maximum extent possible.
+
+        INPUT PARAMETERS
+            S0          -   sparse matrix in any format.
+
+        OUTPUT PARAMETERS
+            S1          -   sparse matrix in SKS format.
+
+        NOTE: if S0 is stored as SKS, it is just copied without conversion.
+
+          -- ALGLIB PROJECT --
+             Copyright 20.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecopytosksbuf(sparsematrix s0,
+            sparsematrix s1,
+            alglib.xparams _params)
+        {
+            double v = 0;
+            int n = 0;
+            int t0 = 0;
+            int t1 = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+
+            alglib.ap.assert((s0.matrixtype==0 || s0.matrixtype==1) || s0.matrixtype==2, "SparseCopyToSKSBuf: invalid matrix type");
+            alglib.ap.assert(s0.m==s0.n, "SparseCopyToSKSBuf: rectangular matrices are not supported");
+            n = s0.n;
+            if( s0.matrixtype==2 )
+            {
+                
+                //
+                // Already SKS, just copy
+                //
+                sparsecopybuf(s0, s1, _params);
+                return;
+            }
+            
+            //
+            // Generate copy of matrix in the SKS format
+            //
+            apserv.ivectorsetlengthatleast(ref s1.didx, n+1, _params);
+            apserv.ivectorsetlengthatleast(ref s1.uidx, n+1, _params);
+            for(i=0; i<=n; i++)
+            {
+                s1.didx[i] = 0;
+                s1.uidx[i] = 0;
+            }
+            t0 = 0;
+            t1 = 0;
+            while( sparseenumerate(s0, ref t0, ref t1, ref i, ref j, ref v, _params) )
+            {
+                if( j<i )
+                {
+                    s1.didx[i] = Math.Max(s1.didx[i], i-j);
+                }
+                else
+                {
+                    s1.uidx[j] = Math.Max(s1.uidx[j], j-i);
+                }
+            }
+            apserv.ivectorsetlengthatleast(ref s1.ridx, n+1, _params);
+            s1.ridx[0] = 0;
+            for(i=1; i<=n; i++)
+            {
+                s1.ridx[i] = s1.ridx[i-1]+s1.didx[i-1]+1+s1.uidx[i-1];
+            }
+            apserv.rvectorsetlengthatleast(ref s1.vals, s1.ridx[n], _params);
+            k = s1.ridx[n];
+            for(i=0; i<=k-1; i++)
+            {
+                s1.vals[i] = 0.0;
+            }
+            t0 = 0;
+            t1 = 0;
+            while( sparseenumerate(s0, ref t0, ref t1, ref i, ref j, ref v, _params) )
+            {
+                if( j<=i )
+                {
+                    s1.vals[s1.ridx[i]+s1.didx[i]-(i-j)] = v;
+                }
+                else
+                {
+                    s1.vals[s1.ridx[j+1]-(j-i)] = v;
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                s1.didx[n] = Math.Max(s1.didx[n], s1.didx[i]);
+                s1.uidx[n] = Math.Max(s1.uidx[n], s1.uidx[i]);
+            }
+            s1.matrixtype = 2;
+            s1.ninitialized = 0;
+            s1.nfree = 0;
+            s1.m = n;
+            s1.n = n;
+        }
+
+
+        /*************************************************************************
+        This non-accessible to user function performs  in-place  creation  of  CRS
+        matrix. It is expected that:
+        * S.M and S.N are initialized
+        * S.RIdx, S.Idx and S.Vals are loaded with values in CRS  format  used  by
+          ALGLIB, with elements of S.Idx/S.Vals  possibly  being  unsorted  within
+          each row (this constructor function may post-sort matrix,  assuming that
+          it is sorted by rows).
+          
+        Only 5 fields should be set by caller. Other fields will be  rewritten  by
+        this constructor function.
+
+        This function performs integrity check on user-specified values, with  the
+        only exception being Vals[] array:
+        * it does not require values to be non-zero
+        * it does not checks for element of Vals[] being finite IEEE-754 values
+
+        INPUT PARAMETERS
+            S   -   sparse matrix with corresponding fields set by caller
+
+        OUTPUT PARAMETERS
+            S   -   sparse matrix in CRS format.
+
+          -- ALGLIB PROJECT --
+             Copyright 20.08.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecreatecrsinplace(sparsematrix s,
+            alglib.xparams _params)
+        {
+            int m = 0;
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int j0 = 0;
+            int j1 = 0;
+
+            m = s.m;
+            n = s.n;
+            
+            //
+            // Perform integrity check
+            //
+            alglib.ap.assert(s.m>0, "SparseCreateCRSInplace: integrity check failed");
+            alglib.ap.assert(s.n>0, "SparseCreateCRSInplace: integrity check failed");
+            alglib.ap.assert(alglib.ap.len(s.ridx)>=m+1, "SparseCreateCRSInplace: integrity check failed");
+            for(i=0; i<=m-1; i++)
+            {
+                alglib.ap.assert(s.ridx[i]>=0 && s.ridx[i]<=s.ridx[i+1], "SparseCreateCRSInplace: integrity check failed");
+            }
+            alglib.ap.assert(s.ridx[m]<=alglib.ap.len(s.idx), "SparseCreateCRSInplace: integrity check failed");
+            alglib.ap.assert(s.ridx[m]<=alglib.ap.len(s.vals), "SparseCreateCRSInplace: integrity check failed");
+            for(i=0; i<=m-1; i++)
+            {
+                j0 = s.ridx[i];
+                j1 = s.ridx[i+1]-1;
+                for(j=j0; j<=j1; j++)
+                {
+                    alglib.ap.assert(s.idx[j]>=0 && s.idx[j]<n, "SparseCreateCRSInplace: integrity check failed");
+                }
+            }
+            
+            //
+            // Initialize
+            //
+            s.matrixtype = 1;
+            s.ninitialized = s.ridx[m];
+            for(i=0; i<=m-1; i++)
+            {
+                tsort.tagsortmiddleir(ref s.idx, ref s.vals, s.ridx[i], s.ridx[i+1]-s.ridx[i], _params);
+            }
+            sparseinitduidx(s, _params);
+        }
+
+
+        /*************************************************************************
+        This function returns type of the matrix storage format.
+
+        INPUT PARAMETERS:
+            S           -   sparse matrix.
+
+        RESULT:
+            sparse storage format used by matrix:
+                0   -   Hash-table
+                1   -   CRS (compressed row storage)
+                2   -   SKS (skyline)
+
+        NOTE: future  versions  of  ALGLIB  may  include additional sparse storage
+              formats.
+
+            
+          -- ALGLIB PROJECT --
+             Copyright 20.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static int sparsegetmatrixtype(sparsematrix s,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            alglib.ap.assert((s.matrixtype==0 || s.matrixtype==1) || s.matrixtype==2, "SparseGetMatrixType: invalid matrix type");
+            result = s.matrixtype;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function checks matrix storage format and returns True when matrix is
+        stored using Hash table representation.
+
+        INPUT PARAMETERS:
+            S   -   sparse matrix.
+
+        RESULT:
+            True if matrix type is Hash table
+            False if matrix type is not Hash table 
+            
+          -- ALGLIB PROJECT --
+             Copyright 20.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool sparseishash(sparsematrix s,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            alglib.ap.assert((s.matrixtype==0 || s.matrixtype==1) || s.matrixtype==2, "SparseIsHash: invalid matrix type");
+            result = s.matrixtype==0;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function checks matrix storage format and returns True when matrix is
+        stored using CRS representation.
+
+        INPUT PARAMETERS:
+            S   -   sparse matrix.
+
+        RESULT:
+            True if matrix type is CRS
+            False if matrix type is not CRS
+            
+          -- ALGLIB PROJECT --
+             Copyright 20.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool sparseiscrs(sparsematrix s,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            alglib.ap.assert((s.matrixtype==0 || s.matrixtype==1) || s.matrixtype==2, "SparseIsCRS: invalid matrix type");
+            result = s.matrixtype==1;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function checks matrix storage format and returns True when matrix is
+        stored using SKS representation.
+
+        INPUT PARAMETERS:
+            S   -   sparse matrix.
+
+        RESULT:
+            True if matrix type is SKS
+            False if matrix type is not SKS
+            
+          -- ALGLIB PROJECT --
+             Copyright 20.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool sparseissks(sparsematrix s,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            alglib.ap.assert((s.matrixtype==0 || s.matrixtype==1) || s.matrixtype==2, "SparseIsSKS: invalid matrix type");
+            result = s.matrixtype==2;
+            return result;
+        }
+
+
+        /*************************************************************************
+        The function frees all memory occupied by  sparse  matrix.  Sparse  matrix
+        structure becomes unusable after this call.
+
+        OUTPUT PARAMETERS
+            S   -   sparse matrix to delete
+            
+          -- ALGLIB PROJECT --
+             Copyright 24.07.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsefree(sparsematrix s,
+            alglib.xparams _params)
+        {
+            s.matrixtype = -1;
+            s.m = 0;
+            s.n = 0;
+            s.nfree = 0;
+            s.ninitialized = 0;
+            s.tablesize = 0;
+        }
+
+
+        /*************************************************************************
+        The function returns number of rows of a sparse matrix.
+
+        RESULT: number of rows of a sparse matrix.
+            
+          -- ALGLIB PROJECT --
+             Copyright 23.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static int sparsegetnrows(sparsematrix s,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = s.m;
+            return result;
+        }
+
+
+        /*************************************************************************
+        The function returns number of columns of a sparse matrix.
+
+        RESULT: number of columns of a sparse matrix.
+            
+          -- ALGLIB PROJECT --
+             Copyright 23.08.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static int sparsegetncols(sparsematrix s,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = s.n;
+            return result;
+        }
+
+
+        /*************************************************************************
+        The function returns number of strictly upper triangular non-zero elements
+        in  the  matrix.  It  counts  SYMBOLICALLY non-zero elements, i.e. entries
+        in the sparse matrix data structure. If some element  has  zero  numerical
+        value, it is still counted.
+
+        This function has different cost for different types of matrices:
+        * for hash-based matrices it involves complete pass over entire hash-table
+          with O(NNZ) cost, where NNZ is number of non-zero elements
+        * for CRS and SKS matrix types cost of counting is O(N) (N - matrix size).
+
+        RESULT: number of non-zero elements strictly above main diagonal
+            
+          -- ALGLIB PROJECT --
+             Copyright 12.02.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static int sparsegetuppercount(sparsematrix s,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int sz = 0;
+            int i0 = 0;
+            int i = 0;
+
+            result = -1;
+            if( s.matrixtype==0 )
+            {
+                
+                //
+                // Hash-table matrix
+                //
+                result = 0;
+                sz = s.tablesize;
+                for(i0=0; i0<=sz-1; i0++)
+                {
+                    i = s.idx[2*i0];
+                    if( i>=0 && s.idx[2*i0+1]>i )
+                    {
+                        result = result+1;
+                    }
+                }
+                return result;
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS matrix
+                //
+                alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseGetUpperCount: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+                result = 0;
+                sz = s.m;
+                for(i=0; i<=sz-1; i++)
+                {
+                    result = result+(s.ridx[i+1]-s.uidx[i]);
+                }
+                return result;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS matrix
+                //
+                alglib.ap.assert(s.m==s.n, "SparseGetUpperCount: non-square SKS matrices are not supported");
+                result = 0;
+                sz = s.m;
+                for(i=0; i<=sz-1; i++)
+                {
+                    result = result+s.uidx[i];
+                }
+                return result;
+            }
+            alglib.ap.assert(false, "SparseGetUpperCount: internal error");
+            return result;
+        }
+
+
+        /*************************************************************************
+        The function returns number of strictly lower triangular non-zero elements
+        in  the  matrix.  It  counts  SYMBOLICALLY non-zero elements, i.e. entries
+        in the sparse matrix data structure. If some element  has  zero  numerical
+        value, it is still counted.
+
+        This function has different cost for different types of matrices:
+        * for hash-based matrices it involves complete pass over entire hash-table
+          with O(NNZ) cost, where NNZ is number of non-zero elements
+        * for CRS and SKS matrix types cost of counting is O(N) (N - matrix size).
+
+        RESULT: number of non-zero elements strictly below main diagonal
+            
+          -- ALGLIB PROJECT --
+             Copyright 12.02.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static int sparsegetlowercount(sparsematrix s,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int sz = 0;
+            int i0 = 0;
+            int i = 0;
+
+            result = -1;
+            if( s.matrixtype==0 )
+            {
+                
+                //
+                // Hash-table matrix
+                //
+                result = 0;
+                sz = s.tablesize;
+                for(i0=0; i0<=sz-1; i0++)
+                {
+                    i = s.idx[2*i0];
+                    if( i>=0 && s.idx[2*i0+1]<i )
+                    {
+                        result = result+1;
+                    }
+                }
+                return result;
+            }
+            if( s.matrixtype==1 )
+            {
+                
+                //
+                // CRS matrix
+                //
+                alglib.ap.assert(s.ninitialized==s.ridx[s.m], "SparseGetUpperCount: some rows/elements of the CRS matrix were not initialized (you must initialize everything you promised to SparseCreateCRS)");
+                result = 0;
+                sz = s.m;
+                for(i=0; i<=sz-1; i++)
+                {
+                    result = result+(s.didx[i]-s.ridx[i]);
+                }
+                return result;
+            }
+            if( s.matrixtype==2 )
+            {
+                
+                //
+                // SKS matrix
+                //
+                alglib.ap.assert(s.m==s.n, "SparseGetUpperCount: non-square SKS matrices are not supported");
+                result = 0;
+                sz = s.m;
+                for(i=0; i<=sz-1; i++)
+                {
+                    result = result+s.didx[i];
+                }
+                return result;
+            }
+            alglib.ap.assert(false, "SparseGetUpperCount: internal error");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This is hash function.
+
+          -- ALGLIB PROJECT --
+             Copyright 14.10.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static int hash(int i,
+            int j,
+            int tabsize,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            hqrnd.hqrndstate r = new hqrnd.hqrndstate();
+
+            hqrnd.hqrndseed(i, j, r, _params);
+            result = hqrnd.hqrnduniformi(r, tabsize, _params);
+            return result;
+        }
+
+
+    }
+    public class ablas
+    {
+        public const int blas2minvendorkernelsize = 8;
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            n1 = 0;
+            n2 = 0;
+
+            if( n>ablasblocksize(a, _params) )
+            {
+                ablasinternalsplitlength(n, ablasblocksize(a, _params), ref n1, ref n2, _params);
+            }
+            else
+            {
+                ablasinternalsplitlength(n, ablasmicroblocksize(_params), ref n1, ref n2, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Complex ABLASSplitLength
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void ablascomplexsplitlength(complex[,] a,
+            int n,
+            ref int n1,
+            ref int n2,
+            alglib.xparams _params)
+        {
+            n1 = 0;
+            n2 = 0;
+
+            if( n>ablascomplexblocksize(a, _params) )
+            {
+                ablasinternalsplitlength(n, ablascomplexblocksize(a, _params), ref n1, ref n2, _params);
+            }
+            else
+            {
+                ablasinternalsplitlength(n, ablasmicroblocksize(_params), ref n1, ref n2, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Returns switch point for parallelism.
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static int gemmparallelsize(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 64;
+            return result;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 24;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Microblock size
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static int ablasmicroblocksize(alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = 8;
+            return result;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            if( ((double)(tau)==(double)(0) || n1>n2) || m1>m2 )
+            {
+                return;
+            }
+            apserv.rvectorsetlengthatleast(ref work, n2-n1+1, _params);
+            rmatrixgemv(n2-n1+1, m2-m1+1, 1.0, c, m1, n1, 1, v, 1, 0.0, work, 0, _params);
+            rmatrixger(m2-m1+1, n2-n1+1, c, m1, n1, -tau, v, 1, work, 0, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            if( ((double)(tau)==(double)(0) || n1>n2) || m1>m2 )
+            {
+                return;
+            }
+            apserv.rvectorsetlengthatleast(ref work, m2-m1+1, _params);
+            rmatrixgemv(m2-m1+1, n2-n1+1, 1.0, c, m1, n1, 0, v, 1, 0.0, work, 0, _params);
+            rmatrixger(m2-m1+1, n2-n1+1, c, m1, n1, -tau, work, 0, v, 1, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int s1 = 0;
+            int s2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m<=2*ablascomplexblocksize(a, _params) && n<=2*ablascomplexblocksize(a, _params) )
+            {
+                
+                //
+                // 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, _params);
+                    cmatrixtranspose(s1, n, a, ia, ja, ref b, ib, jb, _params);
+                    cmatrixtranspose(s2, n, a, ia+s1, ja, ref b, ib, jb+s1, _params);
+                }
+                else
+                {
+                    ablascomplexsplitlength(a, n, ref s1, ref s2, _params);
+                    cmatrixtranspose(m, s1, a, ia, ja, ref b, ib, jb, _params);
+                    cmatrixtranspose(m, s2, a, ia, ja+s1, ref b, ib+s1, jb, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            double[,] b,
+            int ib,
+            int jb,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int s1 = 0;
+            int s2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m<=2*ablasblocksize(a, _params) && n<=2*ablasblocksize(a, _params) )
+            {
+                
+                //
+                // 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, _params);
+                    rmatrixtranspose(s1, n, a, ia, ja, b, ib, jb, _params);
+                    rmatrixtranspose(s2, n, a, ia+s1, ja, b, ib, jb+s1, _params);
+                }
+                else
+                {
+                    ablassplitlength(a, n, ref s1, ref s2, _params);
+                    rmatrixtranspose(m, s1, a, ia, ja, b, ib, jb, _params);
+                    rmatrixtranspose(m, s2, a, ia, ja+s1, b, ib+s1, jb, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This code enforces symmetricy of the matrix by copying Upper part to lower
+        one (or vice versa).
+
+        INPUT PARAMETERS:
+            A   -   matrix
+            N   -   number of rows/columns
+            IsUpper - whether we want to copy upper triangle to lower one (True)
+                    or vice versa (False).
+        *************************************************************************/
+        public static void rmatrixenforcesymmetricity(double[,] a,
+            int n,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            if( isupper )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=i+1; j<=n-1; j++)
+                    {
+                        a[j,i] = a[i,j];
+                    }
+                }
+            }
+            else
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=i+1; j<=n-1; j++)
+                    {
+                        a[i,j] = a[j,i];
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            double[,] b,
+            int ib,
+            int jb,
+            alglib.xparams _params)
+        {
+            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 + alpha*u*v'
+
+        NOTE: this  function  expects  A  to  be  large enough to store result. No
+              automatic preallocation happens for  smaller  arrays.  No  integrity
+              checks is performed for sizes of 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)
+            Alpha-  coefficient
+            U   -   vector #1
+            IU  -   subvector offset
+            V   -   vector #2
+            IV  -   subvector offset
+
+
+          -- ALGLIB routine --
+
+             16.10.2017
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixger(int m,
+            int n,
+            double[,] a,
+            int ia,
+            int ja,
+            double alpha,
+            double[] u,
+            int iu,
+            double[] v,
+            int iv,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Quick exit
+            //
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Try fast kernels:
+            // * vendor kernel
+            // * internal kernel
+            //
+            if( m>blas2minvendorkernelsize && n>blas2minvendorkernelsize )
+            {
+                
+                //
+                // Try MKL kernel first
+                //
+                if( ablasmkl.rmatrixgermkl(m, n, a, ia, ja, alpha, u, iu, v, iv, _params) )
+                {
+                    return;
+                }
+            }
+            if( ablasf.rmatrixgerf(m, n, a, ia, ja, alpha, u, iu, v, iv, _params) )
+            {
+                return;
+            }
+            
+            //
+            // Generic code
+            //
+            for(i=0; i<=m-1; i++)
+            {
+                s = alpha*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 cmatrixrank1(int m,
+            int n,
+            ref complex[,] a,
+            int ia,
+            int ja,
+            ref complex[] u,
+            int iu,
+            ref complex[] v,
+            int iv,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            complex s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Quick exit
+            //
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Try fast kernels:
+            // * vendor kernel
+            // * internal kernel
+            //
+            if( m>blas2minvendorkernelsize && n>blas2minvendorkernelsize )
+            {
+                
+                //
+                // Try MKL kernel first
+                //
+                if( ablasmkl.cmatrixrank1mkl(m, n, ref a, ia, ja, ref u, iu, ref v, iv, _params) )
+                {
+                    return;
+                }
+            }
+            if( ablasf.cmatrixrank1f(m, n, ref a, ia, ja, ref u, iu, ref v, iv, _params) )
+            {
+                return;
+            }
+            
+            //
+            // Generic code
+            //
+            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_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        IMPORTANT: this function is deprecated since ALGLIB 3.13. Use RMatrixGER()
+                   which is more generic version of this function.
+
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Quick exit
+            //
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Try fast kernels:
+            // * vendor kernel
+            // * internal kernel
+            //
+            if( m>blas2minvendorkernelsize && n>blas2minvendorkernelsize )
+            {
+                
+                //
+                // Try MKL kernel first
+                //
+                if( ablasmkl.rmatrixrank1mkl(m, n, a, ia, ja, u, iu, v, iv, _params) )
+                {
+                    return;
+                }
+            }
+            if( ablasf.rmatrixrank1f(m, n, ref a, ia, ja, ref u, iu, ref v, iv, _params) )
+            {
+                return;
+            }
+            
+            //
+            // Generic code
+            //
+            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_];
+                }
+            }
+        }
+
+
+        public static void rmatrixgemv(int m,
+            int n,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            int opa,
+            double[] x,
+            int ix,
+            double beta,
+            double[] y,
+            int iy,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Quick exit for M=0, N=0 or Alpha=0.
+            //
+            // After this block we have M>0, N>0, Alpha<>0.
+            //
+            if( m<=0 )
+            {
+                return;
+            }
+            if( n<=0 || (double)(alpha)==(double)(0.0) )
+            {
+                if( (double)(beta)!=(double)(0) )
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        y[iy+i] = beta*y[iy+i];
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        y[iy+i] = 0.0;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Try fast kernels
+            //
+            if( m>blas2minvendorkernelsize && n>blas2minvendorkernelsize )
+            {
+                
+                //
+                // Try MKL kernel
+                //
+                if( ablasmkl.rmatrixgemvmkl(m, n, alpha, a, ia, ja, opa, x, ix, beta, y, iy, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Generic code
+            //
+            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_];
+                    }
+                    if( (double)(beta)==(double)(0.0) )
+                    {
+                        y[iy+i] = alpha*v;
+                    }
+                    else
+                    {
+                        y[iy+i] = alpha*v+beta*y[iy+i];
+                    }
+                }
+                return;
+            }
+            if( opa==1 )
+            {
+                
+                //
+                // Prepare output array
+                //
+                if( (double)(beta)==(double)(0.0) )
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        y[iy+i] = 0;
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        y[iy+i] = beta*y[iy+i];
+                    }
+                }
+                
+                //
+                // y += A^T*x
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    v = alpha*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;
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            complex v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Quick exit
+            //
+            if( m==0 )
+            {
+                return;
+            }
+            if( n==0 )
+            {
+                for(i=0; i<=m-1; i++)
+                {
+                    y[iy+i] = 0;
+                }
+                return;
+            }
+            
+            //
+            // Try fast kernels
+            //
+            if( m>blas2minvendorkernelsize && n>blas2minvendorkernelsize )
+            {
+                
+                //
+                // Try MKL kernel
+                //
+                if( ablasmkl.cmatrixmvmkl(m, n, a, ia, ja, opa, x, ix, ref y, iy, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Generic code
+            //
+            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;
+            }
+        }
+
+
+        /*************************************************************************
+        IMPORTANT: this function is deprecated since ALGLIB 3.13. Use RMatrixGEMV()
+                   which is more generic version of this function.
+                   
+        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,
+            double[] y,
+            int iy,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            
+            //
+            // Quick exit
+            //
+            if( m==0 )
+            {
+                return;
+            }
+            if( n==0 )
+            {
+                for(i=0; i<=m-1; i++)
+                {
+                    y[iy+i] = 0;
+                }
+                return;
+            }
+            
+            //
+            // Try fast kernels
+            //
+            if( m>blas2minvendorkernelsize && n>blas2minvendorkernelsize )
+            {
+                
+                //
+                // Try MKL kernel
+                //
+                if( ablasmkl.rmatrixmvmkl(m, n, a, ia, ja, opa, x, ix, y, iy, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Generic code
+            //
+            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;
+            }
+        }
+
+
+        public static void rmatrixsymv(int n,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            bool isupper,
+            double[] x,
+            int ix,
+            double beta,
+            double[] y,
+            int iy,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double vr = 0;
+            double vx = 0;
+
+            
+            //
+            // Quick exit for M=0, N=0 or Alpha=0.
+            //
+            // After this block we have M>0, N>0, Alpha<>0.
+            //
+            if( n<=0 )
+            {
+                return;
+            }
+            if( (double)(alpha)==(double)(0.0) )
+            {
+                if( (double)(beta)!=(double)(0) )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        y[iy+i] = beta*y[iy+i];
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        y[iy+i] = 0.0;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Try fast kernels
+            //
+            if( n>blas2minvendorkernelsize )
+            {
+                
+                //
+                // Try MKL kernel
+                //
+                if( ablasmkl.rmatrixsymvmkl(n, alpha, a, ia, ja, isupper, x, ix, beta, y, iy, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Generic code
+            //
+            if( (double)(beta)!=(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    y[iy+i] = beta*y[iy+i];
+                }
+            }
+            else
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    y[iy+i] = 0.0;
+                }
+            }
+            if( isupper )
+            {
+                
+                //
+                // Upper triangle of A is stored
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Process diagonal element
+                    //
+                    v = alpha*a[ia+i,ja+i];
+                    y[iy+i] = y[iy+i]+v*x[ix+i];
+                    
+                    //
+                    // Process off-diagonal elements
+                    //
+                    vr = 0.0;
+                    vx = x[ix+i];
+                    for(j=i+1; j<=n-1; j++)
+                    {
+                        v = alpha*a[ia+i,ja+j];
+                        y[iy+j] = y[iy+j]+v*vx;
+                        vr = vr+v*x[ix+j];
+                    }
+                    y[iy+i] = y[iy+i]+vr;
+                }
+            }
+            else
+            {
+                
+                //
+                // Lower triangle of A is stored
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Process diagonal element
+                    //
+                    v = alpha*a[ia+i,ja+i];
+                    y[iy+i] = y[iy+i]+v*x[ix+i];
+                    
+                    //
+                    // Process off-diagonal elements
+                    //
+                    vr = 0.0;
+                    vx = x[ix+i];
+                    for(j=0; j<=i-1; j++)
+                    {
+                        v = alpha*a[ia+i,ja+j];
+                        y[iy+j] = y[iy+j]+v*vx;
+                        vr = vr+v*x[ix+j];
+                    }
+                    y[iy+i] = y[iy+i]+vr;
+                }
+            }
+        }
+
+
+        public static double rmatrixsyvmv(int n,
+            double[,] a,
+            int ia,
+            int ja,
+            bool isupper,
+            double[] x,
+            int ix,
+            double[] tmp,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int i = 0;
+
+            
+            //
+            // Quick exit for N=0
+            //
+            if( n<=0 )
+            {
+                result = 0;
+                return result;
+            }
+            
+            //
+            // Generic code
+            //
+            rmatrixsymv(n, 1.0, a, ia, ja, isupper, x, ix, 0.0, tmp, 0, _params);
+            result = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                result = result+x[ix+i]*tmp[i];
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine solves linear system op(A)*x=b where:
+        * A is NxN upper/lower triangular/unitriangular matrix
+        * X and B are Nx1 vectors
+        * "op" may be identity transformation, transposition, conjugate transposition
+
+        Solution replaces X.
+
+        IMPORTANT: * no overflow/underflow/denegeracy tests is performed.
+                   * no integrity checks for operand sizes, out-of-bounds accesses
+                     and so on is performed
+
+        INPUT PARAMETERS
+            N   -   matrix size, N>=0
+            A       -   matrix, actial matrix is stored in A[IA:IA+N-1,JA:JA+N-1]
+            IA      -   submatrix offset
+            JA      -   submatrix offset
+            IsUpper -   whether matrix is upper triangular
+            IsUnit  -   whether matrix is unitriangular
+            OpType  -   transformation type:
+                        * 0 - no transformation
+                        * 1 - transposition
+            X       -   right part, actual vector is stored in X[IX:IX+N-1]
+            IX      -   offset
+            
+        OUTPUT PARAMETERS
+            X       -   solution replaces elements X[IX:IX+N-1]
+
+          -- ALGLIB routine / remastering of LAPACK's DTRSV --
+             (c) 2017 Bochkanov Sergey - converted to ALGLIB
+             (c) 2016 Reference BLAS level1 routine (LAPACK version 3.7.0)
+             Reference BLAS is a software package provided by Univ. of Tennessee,
+             Univ. of California Berkeley, Univ. of Colorado Denver and NAG Ltd.
+        *************************************************************************/
+        public static void rmatrixtrsv(int n,
+            double[,] a,
+            int ia,
+            int ja,
+            bool isupper,
+            bool isunit,
+            int optype,
+            double[] x,
+            int ix,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            
+            //
+            // Quick exit
+            //
+            if( n<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Try fast kernels
+            //
+            if( n>blas2minvendorkernelsize )
+            {
+                
+                //
+                // Try MKL kernel
+                //
+                if( ablasmkl.rmatrixtrsvmkl(n, a, ia, ja, isupper, isunit, optype, x, ix, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Generic code
+            //
+            if( optype==0 && isupper )
+            {
+                for(i=n-1; i>=0; i--)
+                {
+                    v = x[ix+i];
+                    for(j=i+1; j<=n-1; j++)
+                    {
+                        v = v-a[ia+i,ja+j]*x[ix+j];
+                    }
+                    if( !isunit )
+                    {
+                        v = v/a[ia+i,ja+i];
+                    }
+                    x[ix+i] = v;
+                }
+                return;
+            }
+            if( optype==0 && !isupper )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    v = x[ix+i];
+                    for(j=0; j<=i-1; j++)
+                    {
+                        v = v-a[ia+i,ja+j]*x[ix+j];
+                    }
+                    if( !isunit )
+                    {
+                        v = v/a[ia+i,ja+i];
+                    }
+                    x[ix+i] = v;
+                }
+                return;
+            }
+            if( optype==1 && isupper )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    v = x[ix+i];
+                    if( !isunit )
+                    {
+                        v = v/a[ia+i,ja+i];
+                    }
+                    x[ix+i] = v;
+                    if( v==0 )
+                    {
+                        continue;
+                    }
+                    for(j=i+1; j<=n-1; j++)
+                    {
+                        x[ix+j] = x[ix+j]-v*a[ia+i,ja+j];
+                    }
+                }
+                return;
+            }
+            if( optype==1 && !isupper )
+            {
+                for(i=n-1; i>=0; i--)
+                {
+                    v = x[ix+i];
+                    if( !isunit )
+                    {
+                        v = v/a[ia+i,ja+i];
+                    }
+                    x[ix+i] = v;
+                    if( v==0 )
+                    {
+                        continue;
+                    }
+                    for(j=0; j<=i-1; j++)
+                    {
+                        x[ix+j] = x[ix+j]-v*a[ia+i,ja+j];
+                    }
+                }
+                return;
+            }
+            alglib.ap.assert(false, "RMatrixTRSV: unexpected operation type");
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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 --
+             20.01.2018
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixrighttrsm(int m,
+            int n,
+            complex[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            complex[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+
+            tsa = apserv.matrixtilesizea(_params)/2;
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( apserv.imax2(m, n, _params)<=tsb )
+            {
+                tscur = tsa;
+            }
+            alglib.ap.assert(tscur>=1, "CMatrixRightTRSM: integrity check failed");
+            
+            //
+            // Upper level parallelization:
+            // * decide whether it is feasible to activate multithreading
+            // * perform optionally parallelized splits on M
+            //
+            if( m>=2*tsb && (double)(4*apserv.rmul3(m, n, n, _params))>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_cmatrixrighttrsm(m,n,a,i1,j1,isupper,isunit,optype,x,i2,j2, _params) )
+                {
+                    return;
+                }
+            }
+            if( m>=2*tsb )
+            {
+                
+                //
+                // Split X: X*A = (X1 X2)^T*A
+                //
+                apserv.tiledsplit(m, tsb, ref s1, ref s2, _params);
+                cmatrixrighttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                cmatrixrighttrsm(s2, n, a, i1, j1, isupper, isunit, optype, x, i2+s1, j2, _params);
+                return;
+            }
+            
+            //
+            // Basecase: either MKL-supported code or ALGLIB basecase code
+            //
+            if( apserv.imax2(m, n, _params)<=tsb )
+            {
+                if( ablasmkl.cmatrixrighttrsmmkl(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params) )
+                {
+                    return;
+                }
+            }
+            if( apserv.imax2(m, n, _params)<=tsa )
+            {
+                cmatrixrighttrsm2(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                return;
+            }
+            
+            //
+            // Recursive subdivision
+            //
+            if( m>=n )
+            {
+                
+                //
+                // Split X: X*A = (X1 X2)^T*A
+                //
+                apserv.tiledsplit(m, tscur, ref s1, ref s2, _params);
+                cmatrixrighttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                cmatrixrighttrsm(s2, n, a, i1, j1, isupper, isunit, optype, x, i2+s1, j2, _params);
+            }
+            else
+            {
+                
+                //
+                // Split A:
+                //               (A1  A12)
+                // X*op(A) = X*op(       )
+                //               (     A2)
+                //
+                // Different variants depending on
+                // IsUpper/OpType combinations
+                //
+                apserv.tiledsplit(n, tscur, ref s1, ref s2, _params);
+                if( isupper && optype==0 )
+                {
+                    
+                    //
+                    //                  (A1  A12)-1
+                    // X*A^-1 = (X1 X2)*(       )
+                    //                  (     A2)
+                    //
+                    cmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                    cmatrixgemm(m, s2, s1, -1.0, x, i2, j2, 0, a, i1, j1+s1, 0, 1.0, x, i2, j2+s1, _params);
+                    cmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _params);
+                }
+                if( isupper && optype!=0 )
+                {
+                    
+                    //
+                    //                  (A1'     )-1
+                    // X*A^-1 = (X1 X2)*(        )
+                    //                  (A12' A2')
+                    //
+                    cmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _params);
+                    cmatrixgemm(m, s1, s2, -1.0, x, i2, j2+s1, 0, a, i1, j1+s1, optype, 1.0, x, i2, j2, _params);
+                    cmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                }
+                if( !isupper && optype==0 )
+                {
+                    
+                    //
+                    //                  (A1     )-1
+                    // X*A^-1 = (X1 X2)*(       )
+                    //                  (A21  A2)
+                    //
+                    cmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _params);
+                    cmatrixgemm(m, s1, s2, -1.0, x, i2, j2+s1, 0, a, i1+s1, j1, 0, 1.0, x, i2, j2, _params);
+                    cmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                }
+                if( !isupper && optype!=0 )
+                {
+                    
+                    //
+                    //                  (A1' A21')-1
+                    // X*A^-1 = (X1 X2)*(        )
+                    //                  (     A2')
+                    //
+                    cmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                    cmatrixgemm(m, s2, s1, -1.0, x, i2, j2, 0, a, i1+s1, j1, optype, 1.0, x, i2, j2+s1, _params);
+                    cmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_cmatrixrighttrsm(int m,
+            int n,
+            complex[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            complex[,] x,
+            int i2,
+            int j2, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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-22.01.2018
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixlefttrsm(int m,
+            int n,
+            complex[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            complex[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+
+            tsa = apserv.matrixtilesizea(_params)/2;
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( apserv.imax2(m, n, _params)<=tsb )
+            {
+                tscur = tsa;
+            }
+            alglib.ap.assert(tscur>=1, "CMatrixLeftTRSM: integrity check failed");
+            
+            //
+            // Upper level parallelization:
+            // * decide whether it is feasible to activate multithreading
+            // * perform optionally parallelized splits on N
+            //
+            if( n>=2*tsb && (double)(4*apserv.rmul3(n, m, m, _params))>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_cmatrixlefttrsm(m,n,a,i1,j1,isupper,isunit,optype,x,i2,j2, _params) )
+                {
+                    return;
+                }
+            }
+            if( n>=2*tsb )
+            {
+                apserv.tiledsplit(n, tscur, ref s1, ref s2, _params);
+                cmatrixlefttrsm(m, s2, a, i1, j1, isupper, isunit, optype, x, i2, j2+s1, _params);
+                cmatrixlefttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                return;
+            }
+            
+            //
+            // Basecase: either MKL-supported code or ALGLIB basecase code
+            //
+            if( apserv.imax2(m, n, _params)<=tsb )
+            {
+                if( ablasmkl.cmatrixlefttrsmmkl(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params) )
+                {
+                    return;
+                }
+            }
+            if( apserv.imax2(m, n, _params)<=tsa )
+            {
+                cmatrixlefttrsm2(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                return;
+            }
+            
+            //
+            // Recursive subdivision
+            //
+            if( n>=m )
+            {
+                
+                //
+                // Split X: op(A)^-1*X = op(A)^-1*(X1 X2)
+                //
+                apserv.tiledsplit(n, tscur, ref s1, ref s2, _params);
+                cmatrixlefttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                cmatrixlefttrsm(m, s2, a, i1, j1, isupper, isunit, optype, x, i2, j2+s1, _params);
+            }
+            else
+            {
+                
+                //
+                // Split A
+                //
+                apserv.tiledsplit(m, tscur, ref s1, ref s2, _params);
+                if( isupper && optype==0 )
+                {
+                    
+                    //
+                    //           (A1  A12)-1  ( X1 )
+                    // A^-1*X* = (       )   *(    )
+                    //           (     A2)    ( X2 )
+                    //
+                    cmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _params);
+                    cmatrixgemm(s1, n, s2, -1.0, a, i1, j1+s1, 0, x, i2+s1, j2, 0, 1.0, x, i2, j2, _params);
+                    cmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                }
+                if( isupper && optype!=0 )
+                {
+                    
+                    //
+                    //          (A1'     )-1 ( X1 )
+                    // A^-1*X = (        )  *(    )
+                    //          (A12' A2')   ( X2 )
+                    //
+                    cmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                    cmatrixgemm(s2, n, s1, -1.0, a, i1, j1+s1, optype, x, i2, j2, 0, 1.0, x, i2+s1, j2, _params);
+                    cmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _params);
+                }
+                if( !isupper && optype==0 )
+                {
+                    
+                    //
+                    //          (A1     )-1 ( X1 )
+                    // A^-1*X = (       )  *(    )
+                    //          (A21  A2)   ( X2 )
+                    //
+                    cmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                    cmatrixgemm(s2, n, s1, -1.0, a, i1+s1, j1, 0, x, i2, j2, 0, 1.0, x, i2+s1, j2, _params);
+                    cmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _params);
+                }
+                if( !isupper && optype!=0 )
+                {
+                    
+                    //
+                    //          (A1' A21')-1 ( X1 )
+                    // A^-1*X = (        )  *(    )
+                    //          (     A2')   ( X2 )
+                    //
+                    cmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _params);
+                    cmatrixgemm(s1, n, s2, -1.0, a, i1+s1, j1, optype, x, i2+s1, j2, 0, 1.0, x, i2, j2, _params);
+                    cmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_cmatrixlefttrsm(int m,
+            int n,
+            complex[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            complex[,] x,
+            int i2,
+            int j2, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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-22.01.2018
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixrighttrsm(int m,
+            int n,
+            double[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            double[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+
+            tsa = apserv.matrixtilesizea(_params);
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( apserv.imax2(m, n, _params)<=tsb )
+            {
+                tscur = tsa;
+            }
+            alglib.ap.assert(tscur>=1, "RMatrixRightTRSM: integrity check failed");
+            
+            //
+            // Upper level parallelization:
+            // * decide whether it is feasible to activate multithreading
+            // * perform optionally parallelized splits on M
+            //
+            if( m>=2*tsb && (double)(apserv.rmul3(m, n, n, _params))>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_rmatrixrighttrsm(m,n,a,i1,j1,isupper,isunit,optype,x,i2,j2, _params) )
+                {
+                    return;
+                }
+            }
+            if( m>=2*tsb )
+            {
+                
+                //
+                // Split X: X*A = (X1 X2)^T*A
+                //
+                apserv.tiledsplit(m, tsb, ref s1, ref s2, _params);
+                rmatrixrighttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                rmatrixrighttrsm(s2, n, a, i1, j1, isupper, isunit, optype, x, i2+s1, j2, _params);
+                return;
+            }
+            
+            //
+            // Basecase: MKL or ALGLIB code
+            //
+            if( apserv.imax2(m, n, _params)<=tsb )
+            {
+                if( ablasmkl.rmatrixrighttrsmmkl(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params) )
+                {
+                    return;
+                }
+            }
+            if( apserv.imax2(m, n, _params)<=tsa )
+            {
+                rmatrixrighttrsm2(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                return;
+            }
+            
+            //
+            // Recursive subdivision
+            //
+            if( m>=n )
+            {
+                
+                //
+                // Split X: X*A = (X1 X2)^T*A
+                //
+                apserv.tiledsplit(m, tscur, ref s1, ref s2, _params);
+                rmatrixrighttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                rmatrixrighttrsm(s2, n, a, i1, j1, isupper, isunit, optype, x, i2+s1, j2, _params);
+            }
+            else
+            {
+                
+                //
+                // Split A:
+                //               (A1  A12)
+                // X*op(A) = X*op(       )
+                //               (     A2)
+                //
+                // Different variants depending on
+                // IsUpper/OpType combinations
+                //
+                apserv.tiledsplit(n, tscur, ref s1, ref s2, _params);
+                if( isupper && optype==0 )
+                {
+                    
+                    //
+                    //                  (A1  A12)-1
+                    // X*A^-1 = (X1 X2)*(       )
+                    //                  (     A2)
+                    //
+                    rmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                    rmatrixgemm(m, s2, s1, -1.0, x, i2, j2, 0, a, i1, j1+s1, 0, 1.0, x, i2, j2+s1, _params);
+                    rmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _params);
+                }
+                if( isupper && optype!=0 )
+                {
+                    
+                    //
+                    //                  (A1'     )-1
+                    // X*A^-1 = (X1 X2)*(        )
+                    //                  (A12' A2')
+                    //
+                    rmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _params);
+                    rmatrixgemm(m, s1, s2, -1.0, x, i2, j2+s1, 0, a, i1, j1+s1, optype, 1.0, x, i2, j2, _params);
+                    rmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                }
+                if( !isupper && optype==0 )
+                {
+                    
+                    //
+                    //                  (A1     )-1
+                    // X*A^-1 = (X1 X2)*(       )
+                    //                  (A21  A2)
+                    //
+                    rmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _params);
+                    rmatrixgemm(m, s1, s2, -1.0, x, i2, j2+s1, 0, a, i1+s1, j1, 0, 1.0, x, i2, j2, _params);
+                    rmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                }
+                if( !isupper && optype!=0 )
+                {
+                    
+                    //
+                    //                  (A1' A21')-1
+                    // X*A^-1 = (X1 X2)*(        )
+                    //                  (     A2')
+                    //
+                    rmatrixrighttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                    rmatrixgemm(m, s2, s1, -1.0, x, i2, j2, 0, a, i1+s1, j1, optype, 1.0, x, i2, j2+s1, _params);
+                    rmatrixrighttrsm(m, s2, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2, j2+s1, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_rmatrixrighttrsm(int m,
+            int n,
+            double[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            double[,] x,
+            int i2,
+            int j2, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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-22.01.2018
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixlefttrsm(int m,
+            int n,
+            double[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            double[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+
+            tsa = apserv.matrixtilesizea(_params);
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( apserv.imax2(m, n, _params)<=tsb )
+            {
+                tscur = tsa;
+            }
+            alglib.ap.assert(tscur>=1, "RMatrixLeftTRSMRec: integrity check failed");
+            
+            //
+            // Upper level parallelization:
+            // * decide whether it is feasible to activate multithreading
+            // * perform optionally parallelized splits on N
+            //
+            if( n>=2*tsb && (double)(apserv.rmul3(n, m, m, _params))>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_rmatrixlefttrsm(m,n,a,i1,j1,isupper,isunit,optype,x,i2,j2, _params) )
+                {
+                    return;
+                }
+            }
+            if( n>=2*tsb )
+            {
+                apserv.tiledsplit(n, tscur, ref s1, ref s2, _params);
+                rmatrixlefttrsm(m, s2, a, i1, j1, isupper, isunit, optype, x, i2, j2+s1, _params);
+                rmatrixlefttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                return;
+            }
+            
+            //
+            // Basecase: MKL or ALGLIB code
+            //
+            if( apserv.imax2(m, n, _params)<=tsb )
+            {
+                if( ablasmkl.rmatrixlefttrsmmkl(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params) )
+                {
+                    return;
+                }
+            }
+            if( apserv.imax2(m, n, _params)<=tsa )
+            {
+                rmatrixlefttrsm2(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                return;
+            }
+            
+            //
+            // Recursive subdivision
+            //
+            if( n>=m )
+            {
+                
+                //
+                // Split X: op(A)^-1*X = op(A)^-1*(X1 X2)
+                //
+                apserv.tiledsplit(n, tscur, ref s1, ref s2, _params);
+                rmatrixlefttrsm(m, s1, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                rmatrixlefttrsm(m, s2, a, i1, j1, isupper, isunit, optype, x, i2, j2+s1, _params);
+            }
+            else
+            {
+                
+                //
+                // Split A
+                //
+                apserv.tiledsplit(m, tscur, ref s1, ref s2, _params);
+                if( isupper && optype==0 )
+                {
+                    
+                    //
+                    //           (A1  A12)-1  ( X1 )
+                    // A^-1*X* = (       )   *(    )
+                    //           (     A2)    ( X2 )
+                    //
+                    rmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _params);
+                    rmatrixgemm(s1, n, s2, -1.0, a, i1, j1+s1, 0, x, i2+s1, j2, 0, 1.0, x, i2, j2, _params);
+                    rmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                }
+                if( isupper && optype!=0 )
+                {
+                    
+                    //
+                    //          (A1'     )-1 ( X1 )
+                    // A^-1*X = (        )  *(    )
+                    //          (A12' A2')   ( X2 )
+                    //
+                    rmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                    rmatrixgemm(s2, n, s1, -1.0, a, i1, j1+s1, optype, x, i2, j2, 0, 1.0, x, i2+s1, j2, _params);
+                    rmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _params);
+                }
+                if( !isupper && optype==0 )
+                {
+                    
+                    //
+                    //          (A1     )-1 ( X1 )
+                    // A^-1*X = (       )  *(    )
+                    //          (A21  A2)   ( X2 )
+                    //
+                    rmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                    rmatrixgemm(s2, n, s1, -1.0, a, i1+s1, j1, 0, x, i2, j2, 0, 1.0, x, i2+s1, j2, _params);
+                    rmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _params);
+                }
+                if( !isupper && optype!=0 )
+                {
+                    
+                    //
+                    //          (A1' A21')-1 ( X1 )
+                    // A^-1*X = (        )  *(    )
+                    //          (     A2')   ( X2 )
+                    //
+                    rmatrixlefttrsm(s2, n, a, i1+s1, j1+s1, isupper, isunit, optype, x, i2+s1, j2, _params);
+                    rmatrixgemm(s1, n, s2, -1.0, a, i1+s1, j1, optype, x, i2+s1, j2, 0, 1.0, x, i2, j2, _params);
+                    rmatrixlefttrsm(s1, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_rmatrixlefttrsm(int m,
+            int n,
+            double[,] a,
+            int i1,
+            int j1,
+            bool isupper,
+            bool isunit,
+            int optype,
+            double[,] x,
+            int i2,
+            int j2, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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:
+        * 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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS
+            N       -   matrix size, N>=0
+            K       -   matrix size, K>=0
+            Alpha   -   coefficient
+            A       -   matrix
+            IA      -   submatrix offset (row index)
+            JA      -   submatrix offset (column index)
+            OpTypeA -   multiplication type:
+                        * 0 - A*A^H is calculated
+                        * 2 - A^H*A is calculated
+            Beta    -   coefficient
+            C       -   preallocated input/output matrix
+            IC      -   submatrix offset (row index)
+            JC      -   submatrix offset (column index)
+            IsUpper -   whether upper or lower triangle of C is updated;
+                        this function updates only one half of C, leaving
+                        other half unchanged (not referenced at all).
+
+          -- ALGLIB routine --
+             16.12.2009-22.01.2018
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixherk(int n,
+            int k,
+            double alpha,
+            complex[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            complex[,] c,
+            int ic,
+            int jc,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+
+            tsa = apserv.matrixtilesizea(_params)/2;
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( apserv.imax2(n, k, _params)<=tsb )
+            {
+                tscur = tsa;
+            }
+            alglib.ap.assert(tscur>=1, "CMatrixHERK: integrity check failed");
+            
+            //
+            // Decide whether it is feasible to activate multithreading
+            //
+            if( n>=2*tsb && (double)(8*apserv.rmul3(k, n, n, _params)/2)>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_cmatrixherk(n,k,alpha,a,ia,ja,optypea,beta,c,ic,jc,isupper, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Use MKL or ALGLIB basecase code
+            //
+            if( apserv.imax2(n, k, _params)<=tsb )
+            {
+                if( ablasmkl.cmatrixherkmkl(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params) )
+                {
+                    return;
+                }
+            }
+            if( apserv.imax2(n, k, _params)<=tsa )
+            {
+                cmatrixherk2(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                return;
+            }
+            
+            //
+            // Recursive division of the problem
+            //
+            if( k>=n )
+            {
+                
+                //
+                // Split K
+                //
+                apserv.tiledsplit(k, tscur, ref s1, ref s2, _params);
+                if( optypea==0 )
+                {
+                    cmatrixherk(n, s1, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                    cmatrixherk(n, s2, alpha, a, ia, ja+s1, optypea, 1.0, c, ic, jc, isupper, _params);
+                }
+                else
+                {
+                    cmatrixherk(n, s1, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                    cmatrixherk(n, s2, alpha, a, ia+s1, ja, optypea, 1.0, c, ic, jc, isupper, _params);
+                }
+            }
+            else
+            {
+                
+                //
+                // Split N
+                //
+                apserv.tiledsplit(n, tscur, ref s1, ref s2, _params);
+                if( optypea==0 && isupper )
+                {
+                    cmatrixherk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                    cmatrixherk(s2, k, alpha, a, ia+s1, ja, optypea, beta, c, ic+s1, jc+s1, isupper, _params);
+                    cmatrixgemm(s1, s2, k, alpha, a, ia, ja, 0, a, ia+s1, ja, 2, beta, c, ic, jc+s1, _params);
+                }
+                if( optypea==0 && !isupper )
+                {
+                    cmatrixherk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                    cmatrixherk(s2, k, alpha, a, ia+s1, ja, optypea, beta, c, ic+s1, jc+s1, isupper, _params);
+                    cmatrixgemm(s2, s1, k, alpha, a, ia+s1, ja, 0, a, ia, ja, 2, beta, c, ic+s1, jc, _params);
+                }
+                if( optypea!=0 && isupper )
+                {
+                    cmatrixherk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                    cmatrixherk(s2, k, alpha, a, ia, ja+s1, optypea, beta, c, ic+s1, jc+s1, isupper, _params);
+                    cmatrixgemm(s1, s2, k, alpha, a, ia, ja, 2, a, ia, ja+s1, 0, beta, c, ic, jc+s1, _params);
+                }
+                if( optypea!=0 && !isupper )
+                {
+                    cmatrixherk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                    cmatrixherk(s2, k, alpha, a, ia, ja+s1, optypea, beta, c, ic+s1, jc+s1, isupper, _params);
+                    cmatrixgemm(s2, s1, k, alpha, a, ia, ja+s1, 2, a, ia, ja, 0, beta, c, ic+s1, jc, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_cmatrixherk(int n,
+            int k,
+            double alpha,
+            complex[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            complex[,] c,
+            int ic,
+            int jc,
+            bool isupper, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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:
+        * 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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS
+            N       -   matrix size, N>=0
+            K       -   matrix size, K>=0
+            Alpha   -   coefficient
+            A       -   matrix
+            IA      -   submatrix offset (row index)
+            JA      -   submatrix offset (column index)
+            OpTypeA -   multiplication type:
+                        * 0 - A*A^T is calculated
+                        * 2 - A^T*A is calculated
+            Beta    -   coefficient
+            C       -   preallocated input/output matrix
+            IC      -   submatrix offset (row index)
+            JC      -   submatrix offset (column index)
+            IsUpper -   whether C is upper triangular or lower triangular
+
+          -- ALGLIB routine --
+             16.12.2009-22.01.2018
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixsyrk(int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            double[,] c,
+            int ic,
+            int jc,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+
+            tsa = apserv.matrixtilesizea(_params);
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( apserv.imax2(n, k, _params)<=tsb )
+            {
+                tscur = tsa;
+            }
+            alglib.ap.assert(tscur>=1, "RMatrixSYRK: integrity check failed");
+            
+            //
+            // Decide whether it is feasible to activate multithreading
+            //
+            if( n>=2*tsb && (double)(2*apserv.rmul3(k, n, n, _params)/2)>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_rmatrixsyrk(n,k,alpha,a,ia,ja,optypea,beta,c,ic,jc,isupper, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Use MKL or generic basecase code
+            //
+            if( apserv.imax2(n, k, _params)<=tsb )
+            {
+                if( ablasmkl.rmatrixsyrkmkl(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params) )
+                {
+                    return;
+                }
+            }
+            if( apserv.imax2(n, k, _params)<=tsa )
+            {
+                rmatrixsyrk2(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                return;
+            }
+            
+            //
+            // Recursive subdivision of the problem
+            //
+            if( k>=n )
+            {
+                
+                //
+                // Split K
+                //
+                apserv.tiledsplit(k, tscur, ref s1, ref s2, _params);
+                if( optypea==0 )
+                {
+                    rmatrixsyrk(n, s1, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                    rmatrixsyrk(n, s2, alpha, a, ia, ja+s1, optypea, 1.0, c, ic, jc, isupper, _params);
+                }
+                else
+                {
+                    rmatrixsyrk(n, s1, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                    rmatrixsyrk(n, s2, alpha, a, ia+s1, ja, optypea, 1.0, c, ic, jc, isupper, _params);
+                }
+            }
+            else
+            {
+                
+                //
+                // Split N
+                //
+                apserv.tiledsplit(n, tscur, ref s1, ref s2, _params);
+                if( optypea==0 && isupper )
+                {
+                    rmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                    rmatrixsyrk(s2, k, alpha, a, ia+s1, ja, optypea, beta, c, ic+s1, jc+s1, isupper, _params);
+                    rmatrixgemm(s1, s2, k, alpha, a, ia, ja, 0, a, ia+s1, ja, 1, beta, c, ic, jc+s1, _params);
+                }
+                if( optypea==0 && !isupper )
+                {
+                    rmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                    rmatrixsyrk(s2, k, alpha, a, ia+s1, ja, optypea, beta, c, ic+s1, jc+s1, isupper, _params);
+                    rmatrixgemm(s2, s1, k, alpha, a, ia+s1, ja, 0, a, ia, ja, 1, beta, c, ic+s1, jc, _params);
+                }
+                if( optypea!=0 && isupper )
+                {
+                    rmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                    rmatrixsyrk(s2, k, alpha, a, ia, ja+s1, optypea, beta, c, ic+s1, jc+s1, isupper, _params);
+                    rmatrixgemm(s1, s2, k, alpha, a, ia, ja, 1, a, ia, ja+s1, 0, beta, c, ic, jc+s1, _params);
+                }
+                if( optypea!=0 && !isupper )
+                {
+                    rmatrixsyrk(s1, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+                    rmatrixsyrk(s2, k, alpha, a, ia, ja+s1, optypea, beta, c, ic+s1, jc+s1, isupper, _params);
+                    rmatrixgemm(s2, s1, k, alpha, a, ia, ja+s1, 1, a, ia, ja, 0, beta, c, ic+s1, jc, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_rmatrixsyrk(int n,
+            int k,
+            double alpha,
+            double[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            double[,] c,
+            int ic,
+            int jc,
+            bool isupper, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        IMPORTANT:
+
+        This function does NOT preallocate output matrix C, it MUST be preallocated
+        by caller prior to calling this function. In case C does not have  enough
+        space to store result, exception will be generated.
+
+        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 (PREALLOCATED, large enough to store result)
+            IC      -   submatrix offset
+            JC      -   submatrix offset
+
+          -- ALGLIB routine --
+             2009-2019
+             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,
+            complex[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            int ts = 0;
+
+            ts = apserv.matrixtilesizeb(_params);
+            
+            //
+            // Check input sizes for correctness
+            //
+            alglib.ap.assert((optypea==0 || optypea==1) || optypea==2, "CMatrixGEMM: incorrect OpTypeA (must be 0 or 1 or 2)");
+            alglib.ap.assert((optypeb==0 || optypeb==1) || optypeb==2, "CMatrixGEMM: incorrect OpTypeB (must be 0 or 1 or 2)");
+            alglib.ap.assert(ic+m<=alglib.ap.rows(c), "CMatrixGEMM: incorect size of output matrix C");
+            alglib.ap.assert(jc+n<=alglib.ap.cols(c), "CMatrixGEMM: incorect size of output matrix C");
+            
+            //
+            // Decide whether it is feasible to activate multithreading
+            //
+            if( (m>=2*ts || n>=2*ts) && (double)(8*apserv.rmul3(m, n, k, _params))>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_cmatrixgemm(m,n,k,alpha,a,ia,ja,optypea,b,ib,jb,optypeb,beta,c,ic,jc, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Start actual work
+            //
+            cmatrixgemmrec(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_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,
+            complex[,] c,
+            int ic,
+            int jc, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        IMPORTANT:
+
+        This function does NOT preallocate output matrix C, it MUST be preallocated
+        by caller prior to calling this function. In case C does not have  enough
+        space to store result, exception will be generated.
+
+        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       -   PREALLOCATED output matrix, large enough to store result
+            IC      -   submatrix offset
+            JC      -   submatrix offset
+
+          -- ALGLIB routine --
+             2009-2019
+             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,
+            double[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            int ts = 0;
+
+            ts = apserv.matrixtilesizeb(_params);
+            
+            //
+            // Check input sizes for correctness
+            //
+            alglib.ap.assert(optypea==0 || optypea==1, "RMatrixGEMM: incorrect OpTypeA (must be 0 or 1)");
+            alglib.ap.assert(optypeb==0 || optypeb==1, "RMatrixGEMM: incorrect OpTypeB (must be 0 or 1)");
+            alglib.ap.assert(ic+m<=alglib.ap.rows(c), "RMatrixGEMM: incorect size of output matrix C");
+            alglib.ap.assert(jc+n<=alglib.ap.cols(c), "RMatrixGEMM: incorect size of output matrix C");
+            
+            //
+            // Decide whether it is feasible to activate multithreading
+            //
+            if( (m>=2*ts || n>=2*ts) && (double)(2*apserv.rmul3(m, n, k, _params))>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_rmatrixgemm(m,n,k,alpha,a,ia,ja,optypea,b,ib,jb,optypeb,beta,c,ic,jc, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Start actual work
+            //
+            rmatrixgemmrec(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_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,
+            double[,] c,
+            int ic,
+            int jc, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        This subroutine is an older version of CMatrixHERK(), one with wrong  name
+        (it is HErmitian update, not SYmmetric). It  is  left  here  for  backward
+        compatibility.
+
+          -- 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,
+            complex[,] c,
+            int ic,
+            int jc,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            cmatrixherk(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params);
+        }
+
+
+        /*************************************************************************
+        Complex ABLASSplitLength
+
+          -- ALGLIB routine --
+             15.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void ablasinternalsplitlength(int n,
+            int nb,
+            ref int n1,
+            ref int n2,
+            alglib.xparams _params)
+        {
+            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,
+            complex[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            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, x, i2, j2, _params) )
+            {
+                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,
+            complex[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            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, x, i2, j2, _params) )
+            {
+                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,
+            double[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            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, x, i2, j2, _params) )
+            {
+                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,
+            double[,] x,
+            int i2,
+            int j2,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double vr = 0;
+            double vd = 0;
+            int i_ = 0;
+
+            
+            //
+            // Special case
+            //
+            if( n==0 || m==0 )
+            {
+                return;
+            }
+            
+            //
+            // Try fast code
+            //
+            if( ablasf.rmatrixlefttrsmf(m, n, a, i1, j1, isupper, isunit, optype, x, i2, j2, _params) )
+            {
+                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 cmatrixherk2(int n,
+            int k,
+            double alpha,
+            complex[,] a,
+            int ia,
+            int ja,
+            int optypea,
+            double beta,
+            complex[,] c,
+            int ic,
+            int jc,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            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.cmatrixherkf(n, k, alpha, a, ia, ja, optypea, beta, c, ic, jc, isupper, _params) )
+            {
+                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_];
+                        }
+                    }
+                }
+                if( (double)(alpha)!=(double)(0) && k>0 )
+                {
+                    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,
+            double[,] c,
+            int ic,
+            int jc,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            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, c, ic, jc, isupper, _params) )
+            {
+                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_];
+                        }
+                    }
+                }
+                if( (double)(alpha)!=(double)(0) && k>0 )
+                {
+                    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;
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine is an actual implementation of CMatrixGEMM.  It  does  not
+        perform some integrity checks performed in the  driver  function,  and  it
+        does not activate multithreading  framework  (driver  decides  whether  to
+        activate workers or not).
+
+          -- ALGLIB routine --
+             10.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void cmatrixgemmrec(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,
+            complex[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+
+            
+            //
+            // Tile hierarchy: B -> A -> A/2
+            //
+            tsa = apserv.matrixtilesizea(_params)/2;
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( apserv.imax3(m, n, k, _params)<=tsb )
+            {
+                tscur = tsa;
+            }
+            alglib.ap.assert(tscur>=1, "CMatrixGEMMRec: integrity check failed");
+            
+            //
+            // Use MKL or ALGLIB basecase code
+            //
+            if( apserv.imax3(m, n, k, _params)<=tsb )
+            {
+                if( ablasmkl.cmatrixgemmmkl(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params) )
+                {
+                    return;
+                }
+            }
+            if( apserv.imax3(m, n, k, _params)<=tsa )
+            {
+                ablasf.cmatrixgemmk(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                return;
+            }
+            
+            //
+            // Recursive algorithm: parallel splitting on M/N
+            //
+            if( m>=n && m>=k )
+            {
+                
+                //
+                // A*B = (A1 A2)^T*B
+                //
+                apserv.tiledsplit(m, tscur, ref s1, ref s2, _params);
+                cmatrixgemmrec(s1, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                if( optypea==0 )
+                {
+                    cmatrixgemmrec(s2, n, k, alpha, a, ia+s1, ja, optypea, b, ib, jb, optypeb, beta, c, ic+s1, jc, _params);
+                }
+                else
+                {
+                    cmatrixgemmrec(s2, n, k, alpha, a, ia, ja+s1, optypea, b, ib, jb, optypeb, beta, c, ic+s1, jc, _params);
+                }
+                return;
+            }
+            if( n>=m && n>=k )
+            {
+                
+                //
+                // A*B = A*(B1 B2)
+                //
+                apserv.tiledsplit(n, tscur, ref s1, ref s2, _params);
+                if( optypeb==0 )
+                {
+                    cmatrixgemmrec(m, s1, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                    cmatrixgemmrec(m, s2, k, alpha, a, ia, ja, optypea, b, ib, jb+s1, optypeb, beta, c, ic, jc+s1, _params);
+                }
+                else
+                {
+                    cmatrixgemmrec(m, s1, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                    cmatrixgemmrec(m, s2, k, alpha, a, ia, ja, optypea, b, ib+s1, jb, optypeb, beta, c, ic, jc+s1, _params);
+                }
+                return;
+            }
+            
+            //
+            // Recursive algorithm: serial splitting on K
+            //
+            
+            //
+            // A*B = (A1 A2)*(B1 B2)^T
+            //
+            apserv.tiledsplit(k, tscur, ref s1, ref s2, _params);
+            if( optypea==0 && optypeb==0 )
+            {
+                cmatrixgemmrec(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                cmatrixgemmrec(m, n, s2, alpha, a, ia, ja+s1, optypea, b, ib+s1, jb, optypeb, 1.0, c, ic, jc, _params);
+            }
+            if( optypea==0 && optypeb!=0 )
+            {
+                cmatrixgemmrec(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                cmatrixgemmrec(m, n, s2, alpha, a, ia, ja+s1, optypea, b, ib, jb+s1, optypeb, 1.0, c, ic, jc, _params);
+            }
+            if( optypea!=0 && optypeb==0 )
+            {
+                cmatrixgemmrec(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                cmatrixgemmrec(m, n, s2, alpha, a, ia+s1, ja, optypea, b, ib+s1, jb, optypeb, 1.0, c, ic, jc, _params);
+            }
+            if( optypea!=0 && optypeb!=0 )
+            {
+                cmatrixgemmrec(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                cmatrixgemmrec(m, n, s2, alpha, a, ia+s1, ja, optypea, b, ib, jb+s1, optypeb, 1.0, c, ic, jc, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_cmatrixgemmrec(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,
+            complex[,] c,
+            int ic,
+            int jc, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        This subroutine is an actual implementation of RMatrixGEMM.  It  does  not
+        perform some integrity checks performed in the  driver  function,  and  it
+        does not activate multithreading  framework  (driver  decides  whether  to
+        activate workers or not).
+
+          -- ALGLIB routine --
+             10.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void rmatrixgemmrec(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,
+            double[,] c,
+            int ic,
+            int jc,
+            alglib.xparams _params)
+        {
+            int s1 = 0;
+            int s2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+
+            tsa = apserv.matrixtilesizea(_params);
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( apserv.imax3(m, n, k, _params)<=tsb )
+            {
+                tscur = tsa;
+            }
+            alglib.ap.assert(tscur>=1, "RMatrixGEMMRec: integrity check failed");
+            
+            //
+            // Use MKL or ALGLIB basecase code
+            //
+            if( (m<=tsb && n<=tsb) && k<=tsb )
+            {
+                if( ablasmkl.rmatrixgemmmkl(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params) )
+                {
+                    return;
+                }
+            }
+            if( (m<=tsa && n<=tsa) && k<=tsa )
+            {
+                ablasf.rmatrixgemmk(m, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                return;
+            }
+            
+            //
+            // Recursive algorithm: split on M or N
+            //
+            if( m>=n && m>=k )
+            {
+                
+                //
+                // A*B = (A1 A2)^T*B
+                //
+                apserv.tiledsplit(m, tscur, ref s1, ref s2, _params);
+                if( optypea==0 )
+                {
+                    rmatrixgemmrec(s2, n, k, alpha, a, ia+s1, ja, optypea, b, ib, jb, optypeb, beta, c, ic+s1, jc, _params);
+                    rmatrixgemmrec(s1, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                }
+                else
+                {
+                    rmatrixgemmrec(s2, n, k, alpha, a, ia, ja+s1, optypea, b, ib, jb, optypeb, beta, c, ic+s1, jc, _params);
+                    rmatrixgemmrec(s1, n, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                }
+                return;
+            }
+            if( n>=m && n>=k )
+            {
+                
+                //
+                // A*B = A*(B1 B2)
+                //
+                apserv.tiledsplit(n, tscur, ref s1, ref s2, _params);
+                if( optypeb==0 )
+                {
+                    rmatrixgemmrec(m, s2, k, alpha, a, ia, ja, optypea, b, ib, jb+s1, optypeb, beta, c, ic, jc+s1, _params);
+                    rmatrixgemmrec(m, s1, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                }
+                else
+                {
+                    rmatrixgemmrec(m, s2, k, alpha, a, ia, ja, optypea, b, ib+s1, jb, optypeb, beta, c, ic, jc+s1, _params);
+                    rmatrixgemmrec(m, s1, k, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                }
+                return;
+            }
+            
+            //
+            // Recursive algorithm: split on K
+            //
+            
+            //
+            // A*B = (A1 A2)*(B1 B2)^T
+            //
+            apserv.tiledsplit(k, tscur, ref s1, ref s2, _params);
+            if( optypea==0 && optypeb==0 )
+            {
+                rmatrixgemmrec(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                rmatrixgemmrec(m, n, s2, alpha, a, ia, ja+s1, optypea, b, ib+s1, jb, optypeb, 1.0, c, ic, jc, _params);
+            }
+            if( optypea==0 && optypeb!=0 )
+            {
+                rmatrixgemmrec(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                rmatrixgemmrec(m, n, s2, alpha, a, ia, ja+s1, optypea, b, ib, jb+s1, optypeb, 1.0, c, ic, jc, _params);
+            }
+            if( optypea!=0 && optypeb==0 )
+            {
+                rmatrixgemmrec(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                rmatrixgemmrec(m, n, s2, alpha, a, ia+s1, ja, optypea, b, ib+s1, jb, optypeb, 1.0, c, ic, jc, _params);
+            }
+            if( optypea!=0 && optypeb!=0 )
+            {
+                rmatrixgemmrec(m, n, s1, alpha, a, ia, ja, optypea, b, ib, jb, optypeb, beta, c, ic, jc, _params);
+                rmatrixgemmrec(m, n, s2, alpha, a, ia+s1, ja, optypea, b, ib, jb+s1, optypeb, 1.0, c, ic, jc, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_rmatrixgemmrec(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,
+            double[,] c,
+            int ic,
+            int jc, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+    }
+    public class dlu
+    {
+        /*************************************************************************
+        Recurrent complex LU subroutine.
+        Never call it directly.
+
+          -- ALGLIB routine --
+             04.01.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixluprec(ref complex[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            ref complex[] tmp,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int m1 = 0;
+            int m2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( Math.Min(m, n)<=ablas.ablascomplexblocksize(a, _params) )
+            {
+                cmatrixlup2(ref a, offs, m, n, ref pivots, ref tmp, _params);
+                return;
+            }
+            if( m>n )
+            {
+                cmatrixluprec(ref a, offs, n, n, ref pivots, ref tmp, _params);
+                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, a, offs+n, offs, _params);
+                return;
+            }
+            ablas.ablascomplexsplitlength(a, m, ref m1, ref m2, _params);
+            cmatrixluprec(ref a, offs, m1, n, ref pivots, ref tmp, _params);
+            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, a, offs+m1, offs, _params);
+                ablas.cmatrixgemm(m-m1, n-m1, m1, -1.0, a, offs+m1, offs, 0, a, offs, offs+m1, 0, 1.0, a, offs+m1, offs+m1, _params);
+                cmatrixluprec(ref a, offs+m1, m-m1, n-m1, ref pivots, ref tmp, _params);
+                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
+        *************************************************************************/
+        public static void rmatrixluprec(ref double[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            ref double[] tmp,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int m1 = 0;
+            int m2 = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( Math.Min(m, n)<=ablas.ablasblocksize(a, _params) )
+            {
+                rmatrixlup2(ref a, offs, m, n, ref pivots, ref tmp, _params);
+                return;
+            }
+            if( m>n )
+            {
+                rmatrixluprec(ref a, offs, n, n, ref pivots, ref tmp, _params);
+                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, a, offs+n, offs, _params);
+                return;
+            }
+            ablas.ablassplitlength(a, m, ref m1, ref m2, _params);
+            rmatrixluprec(ref a, offs, m1, n, ref pivots, ref tmp, _params);
+            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, a, offs+m1, offs, _params);
+                ablas.rmatrixgemm(m-m1, n-m1, m1, -1.0, a, offs+m1, offs, 0, a, offs, offs+m1, 0, 1.0, a, offs+m1, offs+m1, _params);
+                rmatrixluprec(ref a, offs+m1, m-m1, n-m1, ref pivots, ref tmp, _params);
+                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
+        *************************************************************************/
+        public static void cmatrixplurec(ref complex[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            ref complex[] tmp,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            tsa = apserv.matrixtilesizea(_params)/2;
+            tsb = apserv.matrixtilesizeb(_params);
+            if( n<=tsa )
+            {
+                cmatrixplu2(ref a, offs, m, n, ref pivots, ref tmp, _params);
+                return;
+            }
+            if( n>m )
+            {
+                cmatrixplurec(ref a, offs, m, m, ref pivots, ref tmp, _params);
+                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, a, offs, offs+m, _params);
+                return;
+            }
+            if( n>tsb )
+            {
+                n1 = tsb;
+                n2 = n-n1;
+            }
+            else
+            {
+                apserv.tiledsplit(n, tsa, ref n1, ref n2, _params);
+            }
+            cmatrixplurec(ref a, offs, m, n1, ref pivots, ref tmp, _params);
+            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, a, offs, offs+n1, _params);
+                ablas.cmatrixgemm(m-n1, n-n1, n1, -1.0, a, offs+n1, offs, 0, a, offs, offs+n1, 0, 1.0, a, offs+n1, offs+n1, _params);
+                cmatrixplurec(ref a, offs+n1, m-n1, n-n1, ref pivots, ref tmp, _params);
+                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
+        *************************************************************************/
+        public static void rmatrixplurec(ref double[,] a,
+            int offs,
+            int m,
+            int n,
+            ref int[] pivots,
+            ref double[] tmp,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            tsa = apserv.matrixtilesizea(_params);
+            tsb = apserv.matrixtilesizeb(_params);
+            if( n<=tsb )
+            {
+                if( ablasmkl.rmatrixplumkl(ref a, offs, m, n, ref pivots, _params) )
+                {
+                    return;
+                }
+            }
+            if( n<=tsa )
+            {
+                rmatrixplu2(ref a, offs, m, n, ref pivots, ref tmp, _params);
+                return;
+            }
+            if( n>m )
+            {
+                rmatrixplurec(ref a, offs, m, m, ref pivots, ref tmp, _params);
+                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, a, offs, offs+m, _params);
+                return;
+            }
+            if( n>tsb )
+            {
+                n1 = tsb;
+                n2 = n-n1;
+            }
+            else
+            {
+                apserv.tiledsplit(n, tsa, ref n1, ref n2, _params);
+            }
+            rmatrixplurec(ref a, offs, m, n1, ref pivots, ref tmp, _params);
+            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, a, offs, offs+n1, _params);
+                ablas.rmatrixgemm(m-n1, n-n1, n1, -1.0, a, offs+n1, offs, 0, a, offs, offs+n1, 0, 1.0, a, offs+n1, offs+n1, _params);
+                rmatrixplurec(ref a, offs+n1, m-n1, n-n1, ref pivots, ref tmp, _params);
+                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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int jp = 0;
+            complex s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            for(j=0; j<=Math.Min(m-1, n-1); j++)
+            {
+                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_];
+                    }
+                }
+                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_];
+                    }
+                }
+                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, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int jp = 0;
+            double s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            for(j=0; j<=Math.Min(m-1, n-1); j++)
+            {
+                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_];
+                    }
+                }
+                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_];
+                    }
+                }
+                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, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int jp = 0;
+            complex s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            for(j=0; j<=Math.Min(m-1, n-1); j++)
+            {
+                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 )
+                {
+                    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;
+                        }
+                    }
+                    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 )
+                {
+                    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, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int jp = 0;
+            double s = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( m==0 || n==0 )
+            {
+                return;
+            }
+            for(j=0; j<=Math.Min(m-1, n-1); j++)
+            {
+                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) )
+                {
+                    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;
+                        }
+                    }
+                    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 )
+                {
+                    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, _params);
+                }
+            }
+        }
+
+
+    }
+    public class sptrf
+    {
+        /*************************************************************************
+        This structure is used by sparse LU to store "left" and "upper" rectangular
+        submatrices BL and BU, as defined below:
+
+
+            [    |         :       ]
+            [ LU |   BU    :       ]
+            [    |         :       ]
+            [--------------: dense ]
+            [    |         : trail ]
+            [    |  sparse :       ]
+            [ BL |         :       ]
+            [    |  trail  :       ]
+            [    |         :       ]
+            
+
+        *************************************************************************/
+        public class sluv2list1matrix : apobject
+        {
+            public int nfixed;
+            public int ndynamic;
+            public int[] idxfirst;
+            public int[] strgidx;
+            public double[] strgval;
+            public int nallocated;
+            public int nused;
+            public sluv2list1matrix()
+            {
+                init();
+            }
+            public override void init()
+            {
+                idxfirst = new int[0];
+                strgidx = new int[0];
+                strgval = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                sluv2list1matrix _result = new sluv2list1matrix();
+                _result.nfixed = nfixed;
+                _result.ndynamic = ndynamic;
+                _result.idxfirst = (int[])idxfirst.Clone();
+                _result.strgidx = (int[])strgidx.Clone();
+                _result.strgval = (double[])strgval.Clone();
+                _result.nallocated = nallocated;
+                _result.nused = nused;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure is used by sparse LU to store sparse trail submatrix as
+        defined below:
+
+
+            [    |         :       ]
+            [ LU |   BU    :       ]
+            [    |         :       ]
+            [--------------: dense ]
+            [    |         : trail ]
+            [    |  sparse :       ]
+            [ BL |         :       ]
+            [    |  trail  :       ]
+            [    |         :       ]
+            
+
+        *************************************************************************/
+        public class sluv2sparsetrail : apobject
+        {
+            public int n;
+            public int k;
+            public int[] nzc;
+            public int maxwrkcnt;
+            public int maxwrknz;
+            public int wrkcnt;
+            public int[] wrkset;
+            public int[] colid;
+            public bool[] isdensified;
+            public int[] slscolptr;
+            public int[] slsrowptr;
+            public int[] slsidx;
+            public double[] slsval;
+            public int slsused;
+            public double[] tmp0;
+            public sluv2sparsetrail()
+            {
+                init();
+            }
+            public override void init()
+            {
+                nzc = new int[0];
+                wrkset = new int[0];
+                colid = new int[0];
+                isdensified = new bool[0];
+                slscolptr = new int[0];
+                slsrowptr = new int[0];
+                slsidx = new int[0];
+                slsval = new double[0];
+                tmp0 = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                sluv2sparsetrail _result = new sluv2sparsetrail();
+                _result.n = n;
+                _result.k = k;
+                _result.nzc = (int[])nzc.Clone();
+                _result.maxwrkcnt = maxwrkcnt;
+                _result.maxwrknz = maxwrknz;
+                _result.wrkcnt = wrkcnt;
+                _result.wrkset = (int[])wrkset.Clone();
+                _result.colid = (int[])colid.Clone();
+                _result.isdensified = (bool[])isdensified.Clone();
+                _result.slscolptr = (int[])slscolptr.Clone();
+                _result.slsrowptr = (int[])slsrowptr.Clone();
+                _result.slsidx = (int[])slsidx.Clone();
+                _result.slsval = (double[])slsval.Clone();
+                _result.slsused = slsused;
+                _result.tmp0 = (double[])tmp0.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure is used by sparse LU to store dense trail submatrix as
+        defined below:
+
+
+            [    |         :       ]
+            [ LU |   BU    :       ]
+            [    |         :       ]
+            [--------------: dense ]
+            [    |         : trail ]
+            [    |  sparse :       ]
+            [ BL |         :       ]
+            [    |  trail  :       ]
+            [    |         :       ]
+            
+
+        *************************************************************************/
+        public class sluv2densetrail : apobject
+        {
+            public int n;
+            public int ndense;
+            public double[,] d;
+            public int[] did;
+            public sluv2densetrail()
+            {
+                init();
+            }
+            public override void init()
+            {
+                d = new double[0,0];
+                did = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                sluv2densetrail _result = new sluv2densetrail();
+                _result.n = n;
+                _result.ndense = ndense;
+                _result.d = (double[,])d.Clone();
+                _result.did = (int[])did.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure is used by sparse LU for buffer storage
+
+        *************************************************************************/
+        public class sluv2buffer : apobject
+        {
+            public int n;
+            public sparse.sparsematrix sparsel;
+            public sparse.sparsematrix sparseut;
+            public sluv2list1matrix bleft;
+            public sluv2list1matrix bupper;
+            public sluv2sparsetrail strail;
+            public sluv2densetrail dtrail;
+            public int[] rowpermrawidx;
+            public double[,] dbuf;
+            public int[] v0i;
+            public int[] v1i;
+            public double[] v0r;
+            public double[] v1r;
+            public double[] tmp0;
+            public int[] tmpi;
+            public int[] tmpp;
+            public sluv2buffer()
+            {
+                init();
+            }
+            public override void init()
+            {
+                sparsel = new sparse.sparsematrix();
+                sparseut = new sparse.sparsematrix();
+                bleft = new sluv2list1matrix();
+                bupper = new sluv2list1matrix();
+                strail = new sluv2sparsetrail();
+                dtrail = new sluv2densetrail();
+                rowpermrawidx = new int[0];
+                dbuf = new double[0,0];
+                v0i = new int[0];
+                v1i = new int[0];
+                v0r = new double[0];
+                v1r = new double[0];
+                tmp0 = new double[0];
+                tmpi = new int[0];
+                tmpp = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                sluv2buffer _result = new sluv2buffer();
+                _result.n = n;
+                _result.sparsel = (sparse.sparsematrix)sparsel.make_copy();
+                _result.sparseut = (sparse.sparsematrix)sparseut.make_copy();
+                _result.bleft = (sluv2list1matrix)bleft.make_copy();
+                _result.bupper = (sluv2list1matrix)bupper.make_copy();
+                _result.strail = (sluv2sparsetrail)strail.make_copy();
+                _result.dtrail = (sluv2densetrail)dtrail.make_copy();
+                _result.rowpermrawidx = (int[])rowpermrawidx.Clone();
+                _result.dbuf = (double[,])dbuf.Clone();
+                _result.v0i = (int[])v0i.Clone();
+                _result.v1i = (int[])v1i.Clone();
+                _result.v0r = (double[])v0r.Clone();
+                _result.v1r = (double[])v1r.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmpi = (int[])tmpi.Clone();
+                _result.tmpp = (int[])tmpp.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        public const double densebnd = 0.10;
+        public const int slswidth = 8;
+
+
+        /*************************************************************************
+        Sparse LU for square NxN CRS matrix with both row and column permutations.
+
+        Represents A as Pr*L*U*Pc, where:
+        * Pr is a product of row permutations Pr=Pr(0)*Pr(1)*...*Pr(n-2)*Pr(n-1)
+        * Pc is a product of col permutations Pc=Pc(n-1)*Pc(n-2)*...*Pc(1)*Pc(0)
+        * L is lower unitriangular
+        * U is upper triangular
+
+        INPUT PARAMETERS:
+            A           -   sparse square matrix in CRS format
+            PivotType   -   pivot type:
+                            * 0 - for best pivoting available
+                            * 1 - row-only pivoting
+                            * 2 - row and column greedy pivoting  algorithm  (most
+                                  sparse pivot column is selected from the trailing
+                                  matrix at each step)
+            Buf         -   temporary buffer, previously allocated memory is
+                            reused as much as possible
+
+        OUTPUT PARAMETERS:
+            A           -   LU decomposition of A
+            PR          -   array[N], row pivots
+            PC          -   array[N], column pivots
+            Buf         -   following fields of Buf are set:
+                            * Buf.RowPermRawIdx[] - contains row permutation, with
+                              RawIdx[I]=J meaning that J-th row  of  the  original
+                              input matrix was moved to Ith position of the output
+                              factorization
+            
+        This function always succeeds  i.e. it ALWAYS returns valid factorization,
+        but for your convenience it also  returns boolean  value  which  helps  to
+        detect symbolically degenerate matrix:
+        * function returns TRUE if the matrix was factorized AND symbolically
+          non-degenerate
+        * function returns FALSE if the matrix was factorized but U has strictly
+          zero elements at the diagonal (the factorization is returned anyway).
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool sptrflu(sparse.sparsematrix a,
+            int pivottype,
+            ref int[] pr,
+            ref int[] pc,
+            sluv2buffer buf,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int k = 0;
+            int i = 0;
+            int j = 0;
+            int jp = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int ibest = 0;
+            int jbest = 0;
+            double v = 0;
+            double v0 = 0;
+            int nz0 = 0;
+            int nz1 = 0;
+            double uu = 0;
+            int offs = 0;
+            int tmpndense = 0;
+            bool densificationsupported = new bool();
+            int densifyabove = 0;
+
+            alglib.ap.assert(sparse.sparseiscrs(a, _params), "SparseLU: A is not stored in CRS format");
+            alglib.ap.assert(sparse.sparsegetnrows(a, _params)==sparse.sparsegetncols(a, _params), "SparseLU: non-square A");
+            alglib.ap.assert((pivottype==0 || pivottype==1) || pivottype==2, "SparseLU: unexpected pivot type");
+            result = true;
+            n = sparse.sparsegetnrows(a, _params);
+            if( pivottype==0 )
+            {
+                pivottype = 2;
+            }
+            densificationsupported = pivottype==2;
+            
+            //
+            //
+            //
+            buf.n = n;
+            apserv.ivectorsetlengthatleast(ref buf.rowpermrawidx, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                buf.rowpermrawidx[i] = i;
+            }
+            
+            //
+            // Allocate storage for sparse L and U factors
+            //
+            // NOTE: SparseMatrix structure for these factors is only
+            //       partially initialized; we use it just as a temporary
+            //       storage and do not intend to use facilities of the
+            //       'sparse' subpackage to work with these objects.
+            //
+            buf.sparsel.matrixtype = 1;
+            buf.sparsel.m = n;
+            buf.sparsel.n = n;
+            apserv.ivectorsetlengthatleast(ref buf.sparsel.ridx, n+1, _params);
+            buf.sparsel.ridx[0] = 0;
+            buf.sparseut.matrixtype = 1;
+            buf.sparseut.m = n;
+            buf.sparseut.n = n;
+            apserv.ivectorsetlengthatleast(ref buf.sparseut.ridx, n+1, _params);
+            buf.sparseut.ridx[0] = 0;
+            
+            //
+            // Allocate unprocessed yet part of the matrix,
+            // two submatrices:
+            // * BU, upper J rows of columns [J,N), upper submatrix
+            // * BL, left J  cols of rows [J,N), left submatrix
+            // * B1, (N-J)*(N-J) square submatrix
+            //
+            sluv2list1init(n, buf.bleft, _params);
+            sluv2list1init(n, buf.bupper, _params);
+            apserv.ivectorsetlengthatleast(ref pr, n, _params);
+            apserv.ivectorsetlengthatleast(ref pc, n, _params);
+            apserv.ivectorsetlengthatleast(ref buf.v0i, n, _params);
+            apserv.ivectorsetlengthatleast(ref buf.v1i, n, _params);
+            apserv.rvectorsetlengthatleast(ref buf.v0r, n, _params);
+            apserv.rvectorsetlengthatleast(ref buf.v1r, n, _params);
+            sparsetrailinit(a, buf.strail, _params);
+            
+            //
+            // Prepare dense trail, initial densification
+            //
+            densetrailinit(buf.dtrail, n, _params);
+            densifyabove = (int)Math.Round(densebnd*n)+1;
+            if( densificationsupported )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    if( buf.strail.nzc[i]>densifyabove )
+                    {
+                        sparsetraildensify(buf.strail, i, buf.bupper, buf.dtrail, _params);
+                    }
+                }
+            }
+            
+            //
+            // Process sparse part
+            //
+            for(k=0; k<=n-1; k++)
+            {
+                
+                //
+                // Find pivot column and pivot row
+                //
+                if( !sparsetrailfindpivot(buf.strail, pivottype, ref ibest, ref jbest, _params) )
+                {
+                    
+                    //
+                    // Only densified columns are left, break sparse iteration
+                    //
+                    alglib.ap.assert(buf.dtrail.ndense+k==n, "SPTRF: integrity check failed (35741)");
+                    break;
+                }
+                pc[k] = jbest;
+                pr[k] = ibest;
+                j = buf.rowpermrawidx[k];
+                buf.rowpermrawidx[k] = buf.rowpermrawidx[ibest];
+                buf.rowpermrawidx[ibest] = j;
+                
+                //
+                // Apply pivoting to BL and BU
+                //
+                sluv2list1swap(buf.bleft, k, ibest, _params);
+                sluv2list1swap(buf.bupper, k, jbest, _params);
+                
+                //
+                // Apply pivoting to sparse trail, pivot out
+                //
+                sparsetrailpivotout(buf.strail, ibest, jbest, ref uu, buf.v0i, buf.v0r, ref nz0, buf.v1i, buf.v1r, ref nz1, _params);
+                result = result && uu!=0;
+                
+                //
+                // Pivot dense trail
+                //
+                tmpndense = buf.dtrail.ndense;
+                for(i=0; i<=tmpndense-1; i++)
+                {
+                    v = buf.dtrail.d[k,i];
+                    buf.dtrail.d[k,i] = buf.dtrail.d[ibest,i];
+                    buf.dtrail.d[ibest,i] = v;
+                }
+                
+                //
+                // Output to LU matrix
+                //
+                sluv2list1appendsequencetomatrix(buf.bupper, k, true, uu, n, buf.sparseut, k, _params);
+                sluv2list1appendsequencetomatrix(buf.bleft, k, false, 0.0, n, buf.sparsel, k, _params);
+                
+                //
+                // Extract K-th col/row of B1, generate K-th col/row of BL/BU, update NZC
+                //
+                sluv2list1pushsparsevector(buf.bleft, buf.v0i, buf.v0r, nz0, _params);
+                sluv2list1pushsparsevector(buf.bupper, buf.v1i, buf.v1r, nz1, _params);
+                
+                //
+                // Update the rest of the matrix
+                //
+                if( nz0*(nz1+buf.dtrail.ndense)>0 )
+                {
+                    
+                    //
+                    // Update dense trail
+                    //
+                    // NOTE: this update MUST be performed before we update sparse trail,
+                    //       because sparse update may move columns to dense storage after
+                    //       update is performed on them. Thus, we have to avoid applying
+                    //       same update twice.
+                    //
+                    if( buf.dtrail.ndense>0 )
+                    {
+                        tmpndense = buf.dtrail.ndense;
+                        for(i=0; i<=nz0-1; i++)
+                        {
+                            i0 = buf.v0i[i];
+                            v0 = buf.v0r[i];
+                            for(j=0; j<=tmpndense-1; j++)
+                            {
+                                buf.dtrail.d[i0,j] = buf.dtrail.d[i0,j]-v0*buf.dtrail.d[k,j];
+                            }
+                        }
+                    }
+                    
+                    //
+                    // Update sparse trail
+                    //
+                    sparsetrailupdate(buf.strail, buf.v0i, buf.v0r, nz0, buf.v1i, buf.v1r, nz1, buf.bupper, buf.dtrail, densificationsupported, _params);
+                }
+            }
+            
+            //
+            // Process densified trail
+            //
+            if( buf.dtrail.ndense>0 )
+            {
+                tmpndense = buf.dtrail.ndense;
+                
+                //
+                // Generate column pivots to bring actual order of columns in the
+                // working part of the matrix to one used for dense storage
+                //
+                for(i=n-tmpndense; i<=n-1; i++)
+                {
+                    k = buf.dtrail.did[i-(n-tmpndense)];
+                    jp = -1;
+                    for(j=i; j<=n-1; j++)
+                    {
+                        if( buf.strail.colid[j]==k )
+                        {
+                            jp = j;
+                            break;
+                        }
+                    }
+                    alglib.ap.assert(jp>=0, "SPTRF: integrity check failed during reordering");
+                    k = buf.strail.colid[i];
+                    buf.strail.colid[i] = buf.strail.colid[jp];
+                    buf.strail.colid[jp] = k;
+                    pc[i] = jp;
+                }
+                
+                //
+                // Perform dense LU decomposition on dense trail
+                //
+                apserv.rmatrixsetlengthatleast(ref buf.dbuf, buf.dtrail.ndense, buf.dtrail.ndense, _params);
+                for(i=0; i<=tmpndense-1; i++)
+                {
+                    for(j=0; j<=tmpndense-1; j++)
+                    {
+                        buf.dbuf[i,j] = buf.dtrail.d[i+(n-tmpndense),j];
+                    }
+                }
+                apserv.rvectorsetlengthatleast(ref buf.tmp0, 2*n, _params);
+                apserv.ivectorsetlengthatleast(ref buf.tmpp, n, _params);
+                dlu.rmatrixplurec(ref buf.dbuf, 0, tmpndense, tmpndense, ref buf.tmpp, ref buf.tmp0, _params);
+                
+                //
+                // Convert indexes of rows pivots, swap elements of BLeft
+                //
+                for(i=0; i<=tmpndense-1; i++)
+                {
+                    pr[i+(n-tmpndense)] = buf.tmpp[i]+(n-tmpndense);
+                    sluv2list1swap(buf.bleft, i+(n-tmpndense), pr[i+(n-tmpndense)], _params);
+                    j = buf.rowpermrawidx[i+(n-tmpndense)];
+                    buf.rowpermrawidx[i+(n-tmpndense)] = buf.rowpermrawidx[pr[i+(n-tmpndense)]];
+                    buf.rowpermrawidx[pr[i+(n-tmpndense)]] = j;
+                }
+                
+                //
+                // Convert U-factor
+                //
+                apserv.ivectorgrowto(ref buf.sparseut.idx, buf.sparseut.ridx[n-tmpndense]+n*tmpndense, _params);
+                apserv.rvectorgrowto(ref buf.sparseut.vals, buf.sparseut.ridx[n-tmpndense]+n*tmpndense, _params);
+                for(j=0; j<=tmpndense-1; j++)
+                {
+                    offs = buf.sparseut.ridx[j+(n-tmpndense)];
+                    k = n-tmpndense;
+                    
+                    //
+                    // Convert leading N-NDense columns
+                    //
+                    for(i=0; i<=k-1; i++)
+                    {
+                        v = buf.dtrail.d[i,j];
+                        if( v!=0 )
+                        {
+                            buf.sparseut.idx[offs] = i;
+                            buf.sparseut.vals[offs] = v;
+                            offs = offs+1;
+                        }
+                    }
+                    
+                    //
+                    // Convert upper diagonal elements
+                    //
+                    for(i=0; i<=j-1; i++)
+                    {
+                        v = buf.dbuf[i,j];
+                        if( v!=0 )
+                        {
+                            buf.sparseut.idx[offs] = i+(n-tmpndense);
+                            buf.sparseut.vals[offs] = v;
+                            offs = offs+1;
+                        }
+                    }
+                    
+                    //
+                    // Convert diagonal element (always stored)
+                    //
+                    v = buf.dbuf[j,j];
+                    buf.sparseut.idx[offs] = j+(n-tmpndense);
+                    buf.sparseut.vals[offs] = v;
+                    offs = offs+1;
+                    result = result && v!=0;
+                    
+                    //
+                    // Column is done
+                    //
+                    buf.sparseut.ridx[j+(n-tmpndense)+1] = offs;
+                }
+                
+                //
+                // Convert L-factor
+                //
+                apserv.ivectorgrowto(ref buf.sparsel.idx, buf.sparsel.ridx[n-tmpndense]+n*tmpndense, _params);
+                apserv.rvectorgrowto(ref buf.sparsel.vals, buf.sparsel.ridx[n-tmpndense]+n*tmpndense, _params);
+                for(i=0; i<=tmpndense-1; i++)
+                {
+                    sluv2list1appendsequencetomatrix(buf.bleft, i+(n-tmpndense), false, 0.0, n, buf.sparsel, i+(n-tmpndense), _params);
+                    offs = buf.sparsel.ridx[i+(n-tmpndense)+1];
+                    for(j=0; j<=i-1; j++)
+                    {
+                        v = buf.dbuf[i,j];
+                        if( v!=0 )
+                        {
+                            buf.sparsel.idx[offs] = j+(n-tmpndense);
+                            buf.sparsel.vals[offs] = v;
+                            offs = offs+1;
+                        }
+                    }
+                    buf.sparsel.ridx[i+(n-tmpndense)+1] = offs;
+                }
+            }
+            
+            //
+            // Allocate output
+            //
+            apserv.ivectorsetlengthatleast(ref buf.tmpi, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                buf.tmpi[i] = buf.sparsel.ridx[i+1]-buf.sparsel.ridx[i];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                i0 = buf.sparseut.ridx[i];
+                i1 = buf.sparseut.ridx[i+1]-1;
+                for(j=i0; j<=i1; j++)
+                {
+                    k = buf.sparseut.idx[j];
+                    buf.tmpi[k] = buf.tmpi[k]+1;
+                }
+            }
+            a.matrixtype = 1;
+            a.ninitialized = buf.sparsel.ridx[n]+buf.sparseut.ridx[n];
+            a.m = n;
+            a.n = n;
+            apserv.ivectorsetlengthatleast(ref a.ridx, n+1, _params);
+            apserv.ivectorsetlengthatleast(ref a.idx, a.ninitialized, _params);
+            apserv.rvectorsetlengthatleast(ref a.vals, a.ninitialized, _params);
+            a.ridx[0] = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                a.ridx[i+1] = a.ridx[i]+buf.tmpi[i];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                i0 = buf.sparsel.ridx[i];
+                i1 = buf.sparsel.ridx[i+1]-1;
+                jp = a.ridx[i];
+                for(j=i0; j<=i1; j++)
+                {
+                    a.idx[jp+(j-i0)] = buf.sparsel.idx[j];
+                    a.vals[jp+(j-i0)] = buf.sparsel.vals[j];
+                }
+                buf.tmpi[i] = buf.sparsel.ridx[i+1]-buf.sparsel.ridx[i];
+            }
+            apserv.ivectorsetlengthatleast(ref a.didx, n, _params);
+            apserv.ivectorsetlengthatleast(ref a.uidx, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                a.didx[i] = a.ridx[i]+buf.tmpi[i];
+                a.uidx[i] = a.didx[i]+1;
+                buf.tmpi[i] = a.didx[i];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                i0 = buf.sparseut.ridx[i];
+                i1 = buf.sparseut.ridx[i+1]-1;
+                for(j=i0; j<=i1; j++)
+                {
+                    k = buf.sparseut.idx[j];
+                    offs = buf.tmpi[k];
+                    a.idx[offs] = i;
+                    a.vals[offs] = buf.sparseut.vals[j];
+                    buf.tmpi[k] = offs+1;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function initialized rectangular submatrix structure.
+
+        After initialization this structure stores  matrix[N,0],  which contains N
+        rows (sequences), stored as single-linked lists.
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void sluv2list1init(int n,
+            sluv2list1matrix a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(n>=1, "SLUV2List1Init: N<1");
+            a.nfixed = n;
+            a.ndynamic = 0;
+            a.nallocated = n;
+            a.nused = 0;
+            apserv.ivectorgrowto(ref a.idxfirst, n, _params);
+            apserv.ivectorgrowto(ref a.strgidx, 2*a.nallocated, _params);
+            apserv.rvectorgrowto(ref a.strgval, a.nallocated, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                a.idxfirst[i] = -1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function swaps sequences #I and #J stored by the structure
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void sluv2list1swap(sluv2list1matrix a,
+            int i,
+            int j,
+            alglib.xparams _params)
+        {
+            int k = 0;
+
+            k = a.idxfirst[i];
+            a.idxfirst[i] = a.idxfirst[j];
+            a.idxfirst[j] = k;
+        }
+
+
+        /*************************************************************************
+        This function drops sequence #I from the structure
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void sluv2list1dropsequence(sluv2list1matrix a,
+            int i,
+            alglib.xparams _params)
+        {
+            a.idxfirst[i] = -1;
+        }
+
+
+        /*************************************************************************
+        This function appends sequence from the structure to the sparse matrix.
+
+        It is assumed that S is a lower triangular  matrix,  and A stores strictly
+        lower triangular elements (no diagonal ones!). You can explicitly  control
+        whether you want to add diagonal elements or not.
+
+        Output matrix is assumed to be stored in CRS format and  to  be  partially
+        initialized (up to, but not including, Dst-th row). DIdx and UIdx are  NOT
+        updated by this function as well as NInitialized.
+
+        INPUT PARAMETERS:
+            A           -   rectangular matrix structure
+            Src         -   sequence (row or column) index in the structure
+            HasDiagonal -   whether we want to add diagonal element
+            D           -   diagonal element, if HasDiagonal=True
+            NZMAX       -   maximum estimated number of non-zeros in the row,
+                            this function will preallocate storage in the output
+                            matrix.
+            S           -   destination matrix in CRS format, partially initialized
+            Dst         -   destination row index
+
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void sluv2list1appendsequencetomatrix(sluv2list1matrix a,
+            int src,
+            bool hasdiagonal,
+            double d,
+            int nzmax,
+            sparse.sparsematrix s,
+            int dst,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int i0 = 0;
+            int i1 = 0;
+            int jp = 0;
+            int nnz = 0;
+
+            i0 = s.ridx[dst];
+            apserv.ivectorgrowto(ref s.idx, i0+nzmax, _params);
+            apserv.rvectorgrowto(ref s.vals, i0+nzmax, _params);
+            if( hasdiagonal )
+            {
+                i1 = i0+nzmax-1;
+                s.idx[i1] = dst;
+                s.vals[i1] = d;
+                nnz = 1;
+            }
+            else
+            {
+                i1 = i0+nzmax;
+                nnz = 0;
+            }
+            jp = a.idxfirst[src];
+            while( jp>=0 )
+            {
+                i1 = i1-1;
+                s.idx[i1] = a.strgidx[2*jp+1];
+                s.vals[i1] = a.strgval[jp];
+                nnz = nnz+1;
+                jp = a.strgidx[2*jp+0];
+            }
+            for(i=0; i<=nnz-1; i++)
+            {
+                s.idx[i0+i] = s.idx[i1+i];
+                s.vals[i0+i] = s.vals[i1+i];
+            }
+            s.ridx[dst+1] = s.ridx[dst]+nnz;
+        }
+
+
+        /*************************************************************************
+        This function appends sparse column to the  matrix,  increasing  its  size
+        from [N,K] to [N,K+1]
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void sluv2list1pushsparsevector(sluv2list1matrix a,
+            int[] si,
+            double[] sv,
+            int nz,
+            alglib.xparams _params)
+        {
+            int idx = 0;
+            int i = 0;
+            int k = 0;
+            int nused = 0;
+            double v = 0;
+
+            
+            //
+            // Fetch matrix size, increase
+            //
+            k = a.ndynamic;
+            alglib.ap.assert(k<a.nfixed);
+            a.ndynamic = k+1;
+            
+            //
+            // Allocate new storage if needed
+            //
+            nused = a.nused;
+            a.nallocated = Math.Max(a.nallocated, nused+nz);
+            apserv.ivectorgrowto(ref a.strgidx, 2*a.nallocated, _params);
+            apserv.rvectorgrowto(ref a.strgval, a.nallocated, _params);
+            
+            //
+            // Append to list
+            //
+            for(idx=0; idx<=nz-1; idx++)
+            {
+                i = si[idx];
+                v = sv[idx];
+                a.strgidx[2*nused+0] = a.idxfirst[i];
+                a.strgidx[2*nused+1] = k;
+                a.strgval[nused] = v;
+                a.idxfirst[i] = nused;
+                nused = nused+1;
+            }
+            a.nused = nused;
+        }
+
+
+        /*************************************************************************
+        This function initializes dense trail, by default it is matrix[N,0]
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void densetrailinit(sluv2densetrail d,
+            int n,
+            alglib.xparams _params)
+        {
+            int excessivesize = 0;
+
+            
+            //
+            // Note: excessive rows are allocated to accomodate for situation when
+            //       this buffer is used to solve successive problems with increasing
+            //       sizes.
+            //
+            excessivesize = Math.Max((int)Math.Round(1.333*n), n);
+            d.n = n;
+            d.ndense = 0;
+            apserv.ivectorsetlengthatleast(ref d.did, n, _params);
+            if( alglib.ap.rows(d.d)<=excessivesize )
+            {
+                apserv.rmatrixsetlengthatleast(ref d.d, n, 1, _params);
+            }
+            else
+            {
+                d.d = new double[excessivesize, 1];
+            }
+        }
+
+
+        /*************************************************************************
+        This function appends column with id=ID to the dense trail (column IDs are
+        integer numbers in [0,N) which can be used to track column permutations).
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void densetrailappendcolumn(sluv2densetrail d,
+            double[] x,
+            int id,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int targetidx = 0;
+
+            n = d.n;
+            
+            //
+            // Reallocate storage
+            //
+            apserv.rmatrixgrowcolsto(ref d.d, d.ndense+1, n, _params);
+            
+            //
+            // Copy to dense storage:
+            // * BUpper
+            // * BTrail
+            // Remove from sparse storage
+            //
+            targetidx = d.ndense;
+            for(i=0; i<=n-1; i++)
+            {
+                d.d[i,targetidx] = x[i];
+            }
+            d.did[targetidx] = id;
+            d.ndense = targetidx+1;
+        }
+
+
+        /*************************************************************************
+        This function initializes sparse trail from the sparse matrix. By default,
+        sparse trail spans columns and rows in [0,N)  range.  Subsequent  pivoting
+        out of rows/columns changes its range to [K,N), [K+1,N) and so on.
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void sparsetrailinit(sparse.sparsematrix s,
+            sluv2sparsetrail a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int jj = 0;
+            int p = 0;
+            int slsused = 0;
+
+            alglib.ap.assert(s.m==s.n, "SparseTrailInit: M<>N");
+            alglib.ap.assert(s.matrixtype==1, "SparseTrailInit: non-CRS input");
+            n = s.n;
+            a.n = s.n;
+            a.k = 0;
+            apserv.ivectorsetlengthatleast(ref a.nzc, n, _params);
+            apserv.ivectorsetlengthatleast(ref a.colid, n, _params);
+            apserv.rvectorsetlengthatleast(ref a.tmp0, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                a.colid[i] = i;
+            }
+            apserv.bvectorsetlengthatleast(ref a.isdensified, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                a.isdensified[i] = false;
+            }
+            
+            //
+            // Working set of columns
+            //
+            a.maxwrkcnt = apserv.iboundval((int)Math.Round(1+(double)n/(double)3), 1, Math.Min(n, 50), _params);
+            a.wrkcnt = 0;
+            apserv.ivectorsetlengthatleast(ref a.wrkset, a.maxwrkcnt, _params);
+            
+            //
+            // Sparse linked storage (SLS). Store CRS matrix to SLS format,
+            // row by row, starting from the last one.
+            //
+            apserv.ivectorsetlengthatleast(ref a.slscolptr, n, _params);
+            apserv.ivectorsetlengthatleast(ref a.slsrowptr, n, _params);
+            apserv.ivectorsetlengthatleast(ref a.slsidx, s.ridx[n]*slswidth, _params);
+            apserv.rvectorsetlengthatleast(ref a.slsval, s.ridx[n], _params);
+            for(i=0; i<=n-1; i++)
+            {
+                a.nzc[i] = 0;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                a.slscolptr[i] = -1;
+                a.slsrowptr[i] = -1;
+            }
+            slsused = 0;
+            for(i=n-1; i>=0; i--)
+            {
+                j0 = s.ridx[i];
+                j1 = s.ridx[i+1]-1;
+                for(jj=j1; jj>=j0; jj--)
+                {
+                    j = s.idx[jj];
+                    
+                    //
+                    // Update non-zero counts for columns
+                    //
+                    a.nzc[j] = a.nzc[j]+1;
+                    
+                    //
+                    // Insert into column list
+                    //
+                    p = a.slscolptr[j];
+                    if( p>=0 )
+                    {
+                        a.slsidx[p*slswidth+0] = slsused;
+                    }
+                    a.slsidx[slsused*slswidth+0] = -1;
+                    a.slsidx[slsused*slswidth+1] = p;
+                    a.slscolptr[j] = slsused;
+                    
+                    //
+                    // Insert into row list
+                    //
+                    p = a.slsrowptr[i];
+                    if( p>=0 )
+                    {
+                        a.slsidx[p*slswidth+2] = slsused;
+                    }
+                    a.slsidx[slsused*slswidth+2] = -1;
+                    a.slsidx[slsused*slswidth+3] = p;
+                    a.slsrowptr[i] = slsused;
+                    
+                    //
+                    // Store index and value
+                    //
+                    a.slsidx[slsused*slswidth+4] = i;
+                    a.slsidx[slsused*slswidth+5] = j;
+                    a.slsval[slsused] = s.vals[jj];
+                    slsused = slsused+1;
+                }
+            }
+            a.slsused = slsused;
+        }
+
+
+        /*************************************************************************
+        This function searches for a appropriate pivot column/row.
+
+        If there exists non-densified column, it returns indexes of  pivot  column
+        and row, with most sparse column selected for column pivoting, and largest
+        element selected for row pivoting. Function result is True.
+
+        PivotType=1 means that no column pivoting is performed
+        PivotType=2 means that both column and row pivoting are supported
+
+        If all columns were densified, False is returned.
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static bool sparsetrailfindpivot(sluv2sparsetrail a,
+            int pivottype,
+            ref int ipiv,
+            ref int jpiv,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int k = 0;
+            int j = 0;
+            int jp = 0;
+            int entry = 0;
+            int nz = 0;
+            int maxwrknz = 0;
+            int nnzbest = 0;
+            double s = 0;
+            double bbest = 0;
+            int wrk0 = 0;
+            int wrk1 = 0;
+
+            ipiv = 0;
+            jpiv = 0;
+
+            n = a.n;
+            k = a.k;
+            nnzbest = n+1;
+            jpiv = -1;
+            ipiv = -1;
+            result = true;
+            
+            //
+            // Select pivot column
+            //
+            if( pivottype==1 )
+            {
+                
+                //
+                // No column pivoting
+                //
+                alglib.ap.assert(!a.isdensified[k], "SparseTrailFindPivot: integrity check failed");
+                jpiv = k;
+            }
+            else
+            {
+                
+                //
+                // Find pivot column
+                //
+                while( true )
+                {
+                    
+                    //
+                    // Scan working set (if non-empty) for good columns
+                    //
+                    maxwrknz = a.maxwrknz;
+                    for(j=0; j<=a.wrkcnt-1; j++)
+                    {
+                        jp = a.wrkset[j];
+                        if( jp<k )
+                        {
+                            continue;
+                        }
+                        if( a.isdensified[jp] )
+                        {
+                            continue;
+                        }
+                        nz = a.nzc[jp];
+                        if( nz>maxwrknz )
+                        {
+                            continue;
+                        }
+                        if( jpiv<0 || nz<nnzbest )
+                        {
+                            nnzbest = nz;
+                            jpiv = jp;
+                        }
+                    }
+                    if( jpiv>=0 )
+                    {
+                        break;
+                    }
+                    
+                    //
+                    // Well, nothing found. Recompute working set:
+                    // * determine most sparse unprocessed yet column
+                    // * gather all columns with density in [Wrk0,Wrk1) range,
+                    //   increase range, repeat, until working set is full
+                    //
+                    a.wrkcnt = 0;
+                    a.maxwrknz = 0;
+                    wrk0 = n+1;
+                    for(jp=k; jp<=n-1; jp++)
+                    {
+                        if( !a.isdensified[jp] && a.nzc[jp]<wrk0 )
+                        {
+                            wrk0 = a.nzc[jp];
+                        }
+                    }
+                    if( wrk0>n )
+                    {
+                        
+                        //
+                        // Only densified columns are present, exit.
+                        //
+                        result = false;
+                        return result;
+                    }
+                    wrk1 = wrk0+1;
+                    while( a.wrkcnt<a.maxwrkcnt && wrk0<=n )
+                    {
+                        
+                        //
+                        // Find columns with non-zero count in [Wrk0,Wrk1) range
+                        //
+                        for(jp=k; jp<=n-1; jp++)
+                        {
+                            if( a.wrkcnt==a.maxwrkcnt )
+                            {
+                                break;
+                            }
+                            if( a.isdensified[jp] )
+                            {
+                                continue;
+                            }
+                            if( a.nzc[jp]>=wrk0 && a.nzc[jp]<wrk1 )
+                            {
+                                a.wrkset[a.wrkcnt] = jp;
+                                a.wrkcnt = a.wrkcnt+1;
+                                a.maxwrknz = Math.Max(a.maxwrknz, a.nzc[jp]);
+                            }
+                        }
+                        
+                        //
+                        // Advance scan range
+                        //
+                        jp = (int)Math.Round(1.41*(wrk1-wrk0))+1;
+                        wrk0 = wrk1;
+                        wrk1 = wrk0+jp;
+                    }
+                }
+            }
+            
+            //
+            // Select pivot row
+            //
+            bbest = 0;
+            entry = a.slscolptr[jpiv];
+            while( entry>=0 )
+            {
+                s = Math.Abs(a.slsval[entry]);
+                if( ipiv<0 || (double)(s)>(double)(bbest) )
+                {
+                    bbest = s;
+                    ipiv = a.slsidx[entry*slswidth+4];
+                }
+                entry = a.slsidx[entry*slswidth+1];
+            }
+            if( ipiv<0 )
+            {
+                ipiv = k;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function pivots out specified row and column.
+
+        Sparse trail range changes from [K,N) to [K+1,N).
+
+        V0I, V0R, V1I, V1R must be preallocated arrays[N].
+
+        Following data are returned:
+        * UU - diagonal element (pivoted out), can be zero
+        * V0I, V0R, NZ0 - sparse column pivoted out to the left (after permutation
+          is applied to its elements) and divided by UU.
+          V0I is array[NZ0] which stores row indexes in [K+1,N) range, V0R  stores
+          values.
+        * V1I, V1R, NZ1 - sparse row pivoted out to the top.
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void sparsetrailpivotout(sluv2sparsetrail a,
+            int ipiv,
+            int jpiv,
+            ref double uu,
+            int[] v0i,
+            double[] v0r,
+            ref int nz0,
+            int[] v1i,
+            double[] v1r,
+            ref int nz1,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int k = 0;
+            int i = 0;
+            int j = 0;
+            int entry = 0;
+            double v = 0;
+            double s = 0;
+            bool vb = new bool();
+            int pos0k = 0;
+            int pos0piv = 0;
+            int pprev = 0;
+            int pnext = 0;
+            int pnextnext = 0;
+
+            uu = 0;
+            nz0 = 0;
+            nz1 = 0;
+
+            n = a.n;
+            k = a.k;
+            alglib.ap.assert(k<n, "SparseTrailPivotOut: integrity check failed");
+            
+            //
+            // Pivot out column JPiv from the sparse linked storage:
+            // * remove column JPiv from the matrix
+            // * update column K:
+            //   * change element indexes after it is permuted to JPiv
+            //   * resort rows affected by move K->JPiv
+            //
+            // NOTE: this code leaves V0I/V0R/NZ0 in the unfinalized state,
+            //       i.e. these arrays do not account for pivoting performed
+            //       on rows. They will be post-processed later.
+            //
+            nz0 = 0;
+            pos0k = -1;
+            pos0piv = -1;
+            entry = a.slscolptr[jpiv];
+            while( entry>=0 )
+            {
+                
+                //
+                // Offload element
+                //
+                i = a.slsidx[entry*slswidth+4];
+                v0i[nz0] = i;
+                v0r[nz0] = a.slsval[entry];
+                if( i==k )
+                {
+                    pos0k = nz0;
+                }
+                if( i==ipiv )
+                {
+                    pos0piv = nz0;
+                }
+                nz0 = nz0+1;
+                
+                //
+                // Remove element from the row list
+                //
+                pprev = a.slsidx[entry*slswidth+2];
+                pnext = a.slsidx[entry*slswidth+3];
+                if( pprev>=0 )
+                {
+                    a.slsidx[pprev*slswidth+3] = pnext;
+                }
+                else
+                {
+                    a.slsrowptr[i] = pnext;
+                }
+                if( pnext>=0 )
+                {
+                    a.slsidx[pnext*slswidth+2] = pprev;
+                }
+                
+                //
+                // Select next entry
+                //
+                entry = a.slsidx[entry*slswidth+1];
+            }
+            entry = a.slscolptr[k];
+            a.slscolptr[jpiv] = entry;
+            while( entry>=0 )
+            {
+                
+                //
+                // Change column index
+                //
+                a.slsidx[entry*slswidth+5] = jpiv;
+                
+                //
+                // Next entry
+                //
+                entry = a.slsidx[entry*slswidth+1];
+            }
+            
+            //
+            // Post-process V0, account for pivoting.
+            // Compute diagonal element UU.
+            //
+            uu = 0;
+            if( pos0k>=0 || pos0piv>=0 )
+            {
+                
+                //
+                // Apply permutation to rows of pivoted out column, specific
+                // implementation depends on the sparsity at locations #Pos0K
+                // and #Pos0Piv of the V0 array.
+                //
+                if( pos0k>=0 && pos0piv>=0 )
+                {
+                    
+                    //
+                    // Obtain diagonal element
+                    //
+                    uu = v0r[pos0piv];
+                    if( uu!=0 )
+                    {
+                        s = 1/uu;
+                    }
+                    else
+                    {
+                        s = 1;
+                    }
+                    
+                    //
+                    // Move pivoted out element, shift array by one in order
+                    // to remove heading diagonal element (not needed here
+                    // anymore).
+                    //
+                    v0r[pos0piv] = v0r[pos0k];
+                    for(i=0; i<=nz0-2; i++)
+                    {
+                        v0i[i] = v0i[i+1];
+                        v0r[i] = v0r[i+1]*s;
+                    }
+                    nz0 = nz0-1;
+                }
+                if( pos0k>=0 && pos0piv<0 )
+                {
+                    
+                    //
+                    // Diagonal element is zero
+                    //
+                    uu = 0;
+                    
+                    //
+                    // Pivot out element, reorder array
+                    //
+                    v0i[pos0k] = ipiv;
+                    for(i=pos0k; i<=nz0-2; i++)
+                    {
+                        if( v0i[i]<v0i[i+1] )
+                        {
+                            break;
+                        }
+                        j = v0i[i];
+                        v0i[i] = v0i[i+1];
+                        v0i[i+1] = j;
+                        v = v0r[i];
+                        v0r[i] = v0r[i+1];
+                        v0r[i+1] = v;
+                    }
+                }
+                if( pos0k<0 && pos0piv>=0 )
+                {
+                    
+                    //
+                    // Get diagonal element
+                    //
+                    uu = v0r[pos0piv];
+                    if( uu!=0 )
+                    {
+                        s = 1/uu;
+                    }
+                    else
+                    {
+                        s = 1;
+                    }
+                    
+                    //
+                    // Shift array past the pivoted in element by one
+                    // in order to remove pivot
+                    //
+                    for(i=0; i<=pos0piv-1; i++)
+                    {
+                        v0r[i] = v0r[i]*s;
+                    }
+                    for(i=pos0piv; i<=nz0-2; i++)
+                    {
+                        v0i[i] = v0i[i+1];
+                        v0r[i] = v0r[i+1]*s;
+                    }
+                    nz0 = nz0-1;
+                }
+            }
+            
+            //
+            // Pivot out row IPiv from the sparse linked storage:
+            // * remove row IPiv from the matrix
+            // * reindex elements of row K after it is permuted to IPiv
+            // * apply permutation to the cols of the pivoted out row,
+            //   resort columns
+            //
+            nz1 = 0;
+            entry = a.slsrowptr[ipiv];
+            while( entry>=0 )
+            {
+                
+                //
+                // Offload element
+                //
+                j = a.slsidx[entry*slswidth+5];
+                v1i[nz1] = j;
+                v1r[nz1] = a.slsval[entry];
+                nz1 = nz1+1;
+                
+                //
+                // Remove element from the column list
+                //
+                pprev = a.slsidx[entry*slswidth+0];
+                pnext = a.slsidx[entry*slswidth+1];
+                if( pprev>=0 )
+                {
+                    a.slsidx[pprev*slswidth+1] = pnext;
+                }
+                else
+                {
+                    a.slscolptr[j] = pnext;
+                }
+                if( pnext>=0 )
+                {
+                    a.slsidx[pnext*slswidth+0] = pprev;
+                }
+                
+                //
+                // Select next entry
+                //
+                entry = a.slsidx[entry*slswidth+3];
+            }
+            a.slsrowptr[ipiv] = a.slsrowptr[k];
+            entry = a.slsrowptr[ipiv];
+            while( entry>=0 )
+            {
+                
+                //
+                // Change row index
+                //
+                a.slsidx[entry*slswidth+4] = ipiv;
+                
+                //
+                // Resort column affected by row pivoting
+                //
+                j = a.slsidx[entry*slswidth+5];
+                pprev = a.slsidx[entry*slswidth+0];
+                pnext = a.slsidx[entry*slswidth+1];
+                while( pnext>=0 && a.slsidx[pnext*slswidth+4]<ipiv )
+                {
+                    pnextnext = a.slsidx[pnext*slswidth+1];
+                    
+                    //
+                    // prev->next
+                    //
+                    if( pprev>=0 )
+                    {
+                        a.slsidx[pprev*slswidth+1] = pnext;
+                    }
+                    else
+                    {
+                        a.slscolptr[j] = pnext;
+                    }
+                    
+                    //
+                    // entry->prev, entry->next
+                    //
+                    a.slsidx[entry*slswidth+0] = pnext;
+                    a.slsidx[entry*slswidth+1] = pnextnext;
+                    
+                    //
+                    // next->prev, next->next
+                    //
+                    a.slsidx[pnext*slswidth+0] = pprev;
+                    a.slsidx[pnext*slswidth+1] = entry;
+                    
+                    //
+                    // nextnext->prev
+                    //
+                    if( pnextnext>=0 )
+                    {
+                        a.slsidx[pnextnext*slswidth+0] = entry;
+                    }
+                    
+                    //
+                    // PPrev, Item, PNext
+                    //
+                    pprev = pnext;
+                    pnext = pnextnext;
+                }
+                
+                //
+                // Next entry
+                //
+                entry = a.slsidx[entry*slswidth+3];
+            }
+            
+            //
+            // Reorder other structures
+            //
+            i = a.nzc[k];
+            a.nzc[k] = a.nzc[jpiv];
+            a.nzc[jpiv] = i;
+            i = a.colid[k];
+            a.colid[k] = a.colid[jpiv];
+            a.colid[jpiv] = i;
+            vb = a.isdensified[k];
+            a.isdensified[k] = a.isdensified[jpiv];
+            a.isdensified[jpiv] = vb;
+            
+            //
+            // Handle removal of col/row #K
+            //
+            for(i=0; i<=nz1-1; i++)
+            {
+                j = v1i[i];
+                a.nzc[j] = a.nzc[j]-1;
+            }
+            a.k = a.k+1;
+        }
+
+
+        /*************************************************************************
+        This function densifies I1-th column of the sparse trail.
+
+        PARAMETERS:
+            A           -   sparse trail
+            I1          -   column index
+            BUpper      -   upper rectangular submatrix, updated during densification
+                            of the columns (densified columns are removed)
+            DTrail      -   dense trail, receives densified columns from sparse
+                            trail and BUpper
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void sparsetraildensify(sluv2sparsetrail a,
+            int i1,
+            sluv2list1matrix bupper,
+            sluv2densetrail dtrail,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int k = 0;
+            int i = 0;
+            int jp = 0;
+            int entry = 0;
+            int pprev = 0;
+            int pnext = 0;
+
+            n = a.n;
+            k = a.k;
+            alglib.ap.assert(k<n, "SparseTrailDensify: integrity check failed");
+            alglib.ap.assert(k<=i1, "SparseTrailDensify: integrity check failed");
+            alglib.ap.assert(!a.isdensified[i1], "SparseTrailDensify: integrity check failed");
+            
+            //
+            // Offload items [0,K) of densified column from BUpper
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                a.tmp0[i] = 0;
+            }
+            jp = bupper.idxfirst[i1];
+            while( jp>=0 )
+            {
+                a.tmp0[bupper.strgidx[2*jp+1]] = bupper.strgval[jp];
+                jp = bupper.strgidx[2*jp+0];
+            }
+            sluv2list1dropsequence(bupper, i1, _params);
+            
+            //
+            // Offload items [K,N) of densified column from BLeft
+            //
+            entry = a.slscolptr[i1];
+            while( entry>=0 )
+            {
+                
+                //
+                // Offload element
+                //
+                i = a.slsidx[entry*slswidth+4];
+                a.tmp0[i] = a.slsval[entry];
+                
+                //
+                // Remove element from the row list
+                //
+                pprev = a.slsidx[entry*slswidth+2];
+                pnext = a.slsidx[entry*slswidth+3];
+                if( pprev>=0 )
+                {
+                    a.slsidx[pprev*slswidth+3] = pnext;
+                }
+                else
+                {
+                    a.slsrowptr[i] = pnext;
+                }
+                if( pnext>=0 )
+                {
+                    a.slsidx[pnext*slswidth+2] = pprev;
+                }
+                
+                //
+                // Select next entry
+                //
+                entry = a.slsidx[entry*slswidth+1];
+            }
+            
+            //
+            // Densify
+            //
+            a.nzc[i1] = 0;
+            a.isdensified[i1] = true;
+            a.slscolptr[i1] = -1;
+            densetrailappendcolumn(dtrail, a.tmp0, a.colid[i1], _params);
+        }
+
+
+        /*************************************************************************
+        This function appends rank-1 update to the sparse trail.  Dense  trail  is
+        not  updated  here,  but  we  may  move some columns to dense trail during
+        update (i.e. densify them). Thus, you have to update  dense  trail  BEFORE
+        you start updating sparse one (otherwise, recently densified columns  will
+        be updated twice).
+
+        PARAMETERS:
+            A           -   sparse trail
+            V0I, V0R    -   update column returned by SparseTrailPivotOut (MUST be
+                            array[N] independently of the NZ0).
+            NZ0         -   non-zero count for update column
+            V1I, V1R    -   update row returned by SparseTrailPivotOut
+            NZ1         -   non-zero count for update row
+            BUpper      -   upper rectangular submatrix, updated during densification
+                            of the columns (densified columns are removed)
+            DTrail      -   dense trail, receives densified columns from sparse
+                            trail and BUpper
+            DensificationSupported- if False, no densification is performed
+
+          -- ALGLIB routine --
+             15.01.2019
+             Bochkanov Sergey
+        *************************************************************************/
+        private static void sparsetrailupdate(sluv2sparsetrail a,
+            int[] v0i,
+            double[] v0r,
+            int nz0,
+            int[] v1i,
+            double[] v1r,
+            int nz1,
+            sluv2list1matrix bupper,
+            sluv2densetrail dtrail,
+            bool densificationsupported,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int k = 0;
+            int i = 0;
+            int j = 0;
+            int i0 = 0;
+            int i1 = 0;
+            double v1 = 0;
+            int densifyabove = 0;
+            int nnz = 0;
+            int entry = 0;
+            int newentry = 0;
+            int pprev = 0;
+            int pnext = 0;
+            int p = 0;
+            int nexti = 0;
+            int newoffs = 0;
+
+            n = a.n;
+            k = a.k;
+            alglib.ap.assert(k<n, "SparseTrailPivotOut: integrity check failed");
+            densifyabove = (int)Math.Round(densebnd*(n-k))+1;
+            alglib.ap.assert(alglib.ap.len(v0i)>=nz0+1, "SparseTrailUpdate: integrity check failed");
+            alglib.ap.assert(alglib.ap.len(v0r)>=nz0+1, "SparseTrailUpdate: integrity check failed");
+            v0i[nz0] = -1;
+            v0r[nz0] = 0;
+            
+            //
+            // Update sparse representation
+            //
+            apserv.ivectorgrowto(ref a.slsidx, (a.slsused+nz0*nz1)*slswidth, _params);
+            apserv.rvectorgrowto(ref a.slsval, a.slsused+nz0*nz1, _params);
+            for(j=0; j<=nz1-1; j++)
+            {
+                if( nz0==0 )
+                {
+                    continue;
+                }
+                i1 = v1i[j];
+                v1 = v1r[j];
+                
+                //
+                // Update column #I1
+                //
+                nnz = a.nzc[i1];
+                i = 0;
+                i0 = v0i[i];
+                entry = a.slscolptr[i1];
+                pprev = -1;
+                while( i<nz0 )
+                {
+                    
+                    //
+                    // Handle possible fill-in happening BEFORE already existing
+                    // entry of the column list (or simply fill-in, if no entry
+                    // is present).
+                    //
+                    pnext = entry;
+                    if( entry>=0 )
+                    {
+                        nexti = a.slsidx[entry*slswidth+4];
+                    }
+                    else
+                    {
+                        nexti = n+1;
+                    }
+                    while( i<nz0 )
+                    {
+                        if( i0>=nexti )
+                        {
+                            break;
+                        }
+                        
+                        //
+                        // Allocate new entry, store column/row/value
+                        //
+                        newentry = a.slsused;
+                        a.slsused = newentry+1;
+                        nnz = nnz+1;
+                        newoffs = newentry*slswidth;
+                        a.slsidx[newoffs+4] = i0;
+                        a.slsidx[newoffs+5] = i1;
+                        a.slsval[newentry] = -(v1*v0r[i]);
+                        
+                        //
+                        // Insert entry into column list
+                        //
+                        a.slsidx[newoffs+0] = pprev;
+                        a.slsidx[newoffs+1] = pnext;
+                        if( pprev>=0 )
+                        {
+                            a.slsidx[pprev*slswidth+1] = newentry;
+                        }
+                        else
+                        {
+                            a.slscolptr[i1] = newentry;
+                        }
+                        if( entry>=0 )
+                        {
+                            a.slsidx[entry*slswidth+0] = newentry;
+                        }
+                        
+                        //
+                        // Insert entry into row list
+                        //
+                        p = a.slsrowptr[i0];
+                        a.slsidx[newoffs+2] = -1;
+                        a.slsidx[newoffs+3] = p;
+                        if( p>=0 )
+                        {
+                            a.slsidx[p*slswidth+2] = newentry;
+                        }
+                        a.slsrowptr[i0] = newentry;
+                        
+                        //
+                        // Advance pointers
+                        //
+                        pprev = newentry;
+                        i = i+1;
+                        i0 = v0i[i];
+                    }
+                    if( i>=nz0 )
+                    {
+                        break;
+                    }
+                    
+                    //
+                    // Update already existing entry of the column list, if needed
+                    //
+                    if( entry>=0 )
+                    {
+                        if( i0==nexti )
+                        {
+                            a.slsval[entry] = a.slsval[entry]-v1*v0r[i];
+                            i = i+1;
+                            i0 = v0i[i];
+                        }
+                        pprev = entry;
+                    }
+                    
+                    //
+                    // Advance to the next pre-existing entry (if present)
+                    //
+                    if( entry>=0 )
+                    {
+                        entry = a.slsidx[entry*slswidth+1];
+                    }
+                }
+                a.nzc[i1] = nnz;
+                
+                //
+                // Densify column if needed
+                //
+                if( (densificationsupported && nnz>densifyabove) && !a.isdensified[i1] )
+                {
+                    sparsetraildensify(a, i1, bupper, dtrail, _params);
+                }
+            }
+        }
+
+
+    }
+    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]
+
+        NOTE: this function uses algorithm  described  in  Stewart, G. W.  (1980),
+              "The Efficient Generation of  Random  Orthogonal  Matrices  with  an
+              Application to Condition Estimators".
+              
+              Speaking short, to generate an (N+1)x(N+1) orthogonal matrix, it:
+              * takes an NxN one
+              * takes uniformly distributed unit vector of dimension N+1.
+              * constructs a Householder reflection from the vector, then applies
+                it to the smaller matrix (embedded in the larger size with a 1 at
+                the bottom right corner).
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixrndorthogonal(int n,
+            ref double[,] a,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double l1 = 0;
+            double l2 = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+
+            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;
+            }
+            hqrnd.hqrndrandomize(rs, _params);
+            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(hqrnd.hqrnduniformr(rs, _params)*(l2-l1)+l1);
+            }
+            a[n-1,n-1] = Math.Exp(l2);
+            rmatrixrndorthogonalfromtheleft(ref a, n, n, _params);
+            rmatrixrndorthogonalfromtheright(ref a, n, n, _params);
+        }
+
+
+        /*************************************************************************
+        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]
+
+        NOTE: this function uses algorithm  described  in  Stewart, G. W.  (1980),
+              "The Efficient Generation of  Random  Orthogonal  Matrices  with  an
+              Application to Condition Estimators".
+              
+              Speaking short, to generate an (N+1)x(N+1) orthogonal matrix, it:
+              * takes an NxN one
+              * takes uniformly distributed unit vector of dimension N+1.
+              * constructs a Householder reflection from the vector, then applies
+                it to the smaller matrix (embedded in the larger size with a 1 at
+                the bottom right corner).
+
+          -- ALGLIB routine --
+             04.12.2009
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixrndorthogonal(int n,
+            ref complex[,] a,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                hqrnd.hqrndunit2(state, ref v.x, ref v.y, _params);
+                a[0,0] = v;
+                return;
+            }
+            hqrnd.hqrndrandomize(state, _params);
+            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(hqrnd.hqrnduniformr(state, _params)*(l2-l1)+l1);
+            }
+            a[n-1,n-1] = Math.Exp(l2);
+            cmatrixrndorthogonalfromtheleft(ref a, n, n, _params);
+            cmatrixrndorthogonalfromtheright(ref a, n, n, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double l1 = 0;
+            double l2 = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+
+            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
+            //
+            hqrnd.hqrndrandomize(rs, _params);
+            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*hqrnd.hqrnduniformi(rs, 2, _params)-1)*Math.Exp(hqrnd.hqrnduniformr(rs, _params)*(l2-l1)+l1);
+            }
+            a[n-1,n-1] = Math.Exp(l2);
+            
+            //
+            // Multiply
+            //
+            smatrixrndmultiply(ref a, n, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double l1 = 0;
+            double l2 = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+
+            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
+            //
+            hqrnd.hqrndrandomize(rs, _params);
+            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(hqrnd.hqrnduniformr(rs, _params)*(l2-l1)+l1);
+            }
+            a[n-1,n-1] = Math.Exp(l2);
+            
+            //
+            // Multiply
+            //
+            smatrixrndmultiply(ref a, n, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double l1 = 0;
+            double l2 = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+
+            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
+            //
+            hqrnd.hqrndrandomize(rs, _params);
+            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*hqrnd.hqrnduniformi(rs, 2, _params)-1)*Math.Exp(hqrnd.hqrnduniformr(rs, _params)*(l2-l1)+l1);
+            }
+            a[n-1,n-1] = Math.Exp(l2);
+            
+            //
+            // Multiply
+            //
+            hmatrixrndmultiply(ref a, n, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double l1 = 0;
+            double l2 = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+
+            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
+            //
+            hqrnd.hqrndrandomize(rs, _params);
+            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(hqrnd.hqrnduniformr(rs, _params)*(l2-l1)+l1);
+            }
+            a[n-1,n-1] = Math.Exp(l2);
+            
+            //
+            // Multiply
+            //
+            hmatrixrndmultiply(ref a, n, _params);
+            
+            //
+            // 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            for(s=2; s<=n; s++)
+            {
+                
+                //
+                // Prepare random normal v
+                //
+                do
+                {
+                    i = 1;
+                    while( i<=s )
+                    {
+                        hqrnd.hqrndnormal2(state, ref u1, ref u2, _params);
+                        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
+                //
+                ablas.generatereflection(ref v, s, ref tau, _params);
+                v[1] = 1;
+                ablas.applyreflectionfromtheright(ref a, tau, v, 0, m-1, n-s, n-1, ref w, _params);
+            }
+            
+            //
+            // Second pass.
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                tau = 2*hqrnd.hqrnduniformi(state, 2, _params)-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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            for(s=2; s<=m; s++)
+            {
+                
+                //
+                // Prepare random normal v
+                //
+                do
+                {
+                    i = 1;
+                    while( i<=s )
+                    {
+                        hqrnd.hqrndnormal2(state, ref u1, ref u2, _params);
+                        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
+                //
+                ablas.generatereflection(ref v, s, ref tau, _params);
+                v[1] = 1;
+                ablas.applyreflectionfromtheleft(ref a, tau, v, m-s, m-1, 0, n-1, ref w, _params);
+            }
+            
+            //
+            // Second pass.
+            //
+            for(i=0; i<=m-1; i++)
+            {
+                tau = 2*hqrnd.hqrnduniformi(state, 2, _params)-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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                hqrnd.hqrndunit2(state, ref tau.x, ref tau.y, _params);
+                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, _params);
+            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, _params);
+                        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, _params);
+                v[1] = 1;
+                creflections.complexapplyreflectionfromtheright(ref a, tau, ref v, 0, m-1, n-s, n-1, ref w, _params);
+            }
+            
+            //
+            // Second pass.
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                hqrnd.hqrndunit2(state, ref tau.x, ref tau.y, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                hqrnd.hqrndunit2(state, ref tau.x, ref tau.y, _params);
+                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, _params);
+            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, _params);
+                        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, _params);
+                v[1] = 1;
+                creflections.complexapplyreflectionfromtheleft(ref a, tau, v, m-s, m-1, 0, n-1, ref w, _params);
+            }
+            
+            //
+            // Second pass.
+            //
+            for(i=0; i<=m-1; i++)
+            {
+                hqrnd.hqrndunit2(state, ref tau.x, ref tau.y, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            for(s=2; s<=n; s++)
+            {
+                
+                //
+                // Prepare random normal v
+                //
+                do
+                {
+                    i = 1;
+                    while( i<=s )
+                    {
+                        hqrnd.hqrndnormal2(state, ref u1, ref u2, _params);
+                        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
+                //
+                ablas.generatereflection(ref v, s, ref tau, _params);
+                v[1] = 1;
+                ablas.applyreflectionfromtheright(ref a, tau, v, 0, n-1, n-s, n-1, ref w, _params);
+                ablas.applyreflectionfromtheleft(ref a, tau, v, n-s, n-1, 0, n-1, ref w, _params);
+            }
+            
+            //
+            // Second pass.
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                tau = 2*hqrnd.hqrnduniformi(state, 2, _params)-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_];
+                }
+            }
+            
+            //
+            // Copy upper triangle to lower
+            //
+            for(i=0; i<=n-2; i++)
+            {
+                for(i_=i+1; i_<=n-1;i_++)
+                {
+                    a[i_,i] = 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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, _params);
+                        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, _params);
+                v[1] = 1;
+                creflections.complexapplyreflectionfromtheright(ref a, tau, ref v, 0, n-1, n-s, n-1, ref w, _params);
+                creflections.complexapplyreflectionfromtheleft(ref a, math.conj(tau), v, n-s, n-1, 0, n-1, ref w, _params);
+            }
+            
+            //
+            // Second pass.
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                hqrnd.hqrndunit2(state, ref tau.x, ref tau.y, _params);
+                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_];
+                }
+            }
+            
+            //
+            // Change all values from lower triangle by complex-conjugate values
+            // from upper one
+            //
+            for(i=0; i<=n-2; i++)
+            {
+                for(i_=i+1; i_<=n-1;i_++)
+                {
+                    a[i_,i] = a[i,i_];
+                }
+            }
+            for(s=0; s<=n-2; s++)
+            {
+                for(i=s+1; i<=n-1; i++)
+                {
+                    a[i,s].y = -a[i,s].y;
+                }
+            }
+        }
+
+
+    }
+    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]
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+          
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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]
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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' denotes conj(X^T)).
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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-22.01.2018
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool hpdmatrixcholesky(ref complex[,] a,
+            int n,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Update of Cholesky decomposition: rank-1 update to original A.  "Buffered"
+        version which uses preallocated buffer which is saved  between  subsequent
+        function calls.
+
+        This function uses internally allocated buffer which is not saved  between
+        subsequent  calls.  So,  if  you  perform  a lot  of  subsequent  updates,
+        we  recommend   you   to   use   "buffered"   version   of  this function:
+        SPDMatrixCholeskyUpdateAdd1Buf().
+
+        INPUT PARAMETERS:
+            A       -   upper or lower Cholesky factor.
+                        array with elements [0..N-1, 0..N-1].
+                        Exception is thrown if array size is too small.
+            N       -   size of matrix A, N>0
+            IsUpper -   if IsUpper=True, then A contains  upper  Cholesky  factor;
+                        otherwise A contains a lower one.
+            U       -   array[N], rank-1 update to A: A_mod = A + u*u'
+                        Exception is thrown if array size is too small.
+            BufR    -   possibly preallocated  buffer;  automatically  resized  if
+                        needed. It is recommended to  reuse  this  buffer  if  you
+                        perform a lot of subsequent decompositions.
+
+        OUTPUT PARAMETERS:
+            A       -   updated factorization.  If  IsUpper=True,  then  the  upper
+                        triangle contains matrix U, and the elements below the main
+                        diagonal are not modified. Similarly, if IsUpper = False.
+                        
+        NOTE: this function always succeeds, so it does not return completion code
+
+        NOTE: this function checks sizes of input arrays, but it does  NOT  checks
+              for presence of infinities or NAN's.
+
+          -- ALGLIB --
+             03.02.2014
+             Sergey Bochkanov
+        *************************************************************************/
+        public static void spdmatrixcholeskyupdateadd1(double[,] a,
+            int n,
+            bool isupper,
+            double[] u,
+            alglib.xparams _params)
+        {
+            double[] bufr = new double[0];
+
+            alglib.ap.assert(n>0, "SPDMatrixCholeskyUpdateAdd1: N<=0");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "SPDMatrixCholeskyUpdateAdd1: Rows(A)<N");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "SPDMatrixCholeskyUpdateAdd1: Cols(A)<N");
+            alglib.ap.assert(alglib.ap.len(u)>=n, "SPDMatrixCholeskyUpdateAdd1: Length(U)<N");
+            spdmatrixcholeskyupdateadd1buf(a, n, isupper, u, ref bufr, _params);
+        }
+
+
+        /*************************************************************************
+        Update of Cholesky decomposition: "fixing" some variables.
+
+        This function uses internally allocated buffer which is not saved  between
+        subsequent  calls.  So,  if  you  perform  a lot  of  subsequent  updates,
+        we  recommend   you   to   use   "buffered"   version   of  this function:
+        SPDMatrixCholeskyUpdateFixBuf().
+
+        "FIXING" EXPLAINED:
+
+            Suppose we have N*N positive definite matrix A. "Fixing" some variable
+            means filling corresponding row/column of  A  by  zeros,  and  setting
+            diagonal element to 1.
+            
+            For example, if we fix 2nd variable in 4*4 matrix A, it becomes Af:
+            
+                ( A00  A01  A02  A03 )      ( Af00  0   Af02 Af03 )
+                ( A10  A11  A12  A13 )      (  0    1    0    0   )
+                ( A20  A21  A22  A23 )  =>  ( Af20  0   Af22 Af23 )
+                ( A30  A31  A32  A33 )      ( Af30  0   Af32 Af33 )
+            
+            If we have Cholesky decomposition of A, it must be recalculated  after
+            variables were  fixed.  However,  it  is  possible  to  use  efficient
+            algorithm, which needs O(K*N^2)  time  to  "fix"  K  variables,  given
+            Cholesky decomposition of original, "unfixed" A.
+
+        INPUT PARAMETERS:
+            A       -   upper or lower Cholesky factor.
+                        array with elements [0..N-1, 0..N-1].
+                        Exception is thrown if array size is too small.
+            N       -   size of matrix A, N>0
+            IsUpper -   if IsUpper=True, then A contains  upper  Cholesky  factor;
+                        otherwise A contains a lower one.
+            Fix     -   array[N], I-th element is True if I-th  variable  must  be
+                        fixed. Exception is thrown if array size is too small.
+            BufR    -   possibly preallocated  buffer;  automatically  resized  if
+                        needed. It is recommended to  reuse  this  buffer  if  you
+                        perform a lot of subsequent decompositions.
+
+        OUTPUT PARAMETERS:
+            A       -   updated factorization.  If  IsUpper=True,  then  the  upper
+                        triangle contains matrix U, and the elements below the main
+                        diagonal are not modified. Similarly, if IsUpper = False.
+                        
+        NOTE: this function always succeeds, so it does not return completion code
+
+        NOTE: this function checks sizes of input arrays, but it does  NOT  checks
+              for presence of infinities or NAN's.
+              
+        NOTE: this  function  is  efficient  only  for  moderate amount of updated
+              variables - say, 0.1*N or 0.3*N. For larger amount of  variables  it
+              will  still  work,  but  you  may  get   better   performance   with
+              straightforward Cholesky.
+
+          -- ALGLIB --
+             03.02.2014
+             Sergey Bochkanov
+        *************************************************************************/
+        public static void spdmatrixcholeskyupdatefix(double[,] a,
+            int n,
+            bool isupper,
+            bool[] fix,
+            alglib.xparams _params)
+        {
+            double[] bufr = new double[0];
+
+            alglib.ap.assert(n>0, "SPDMatrixCholeskyUpdateFix: N<=0");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "SPDMatrixCholeskyUpdateFix: Rows(A)<N");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "SPDMatrixCholeskyUpdateFix: Cols(A)<N");
+            alglib.ap.assert(alglib.ap.len(fix)>=n, "SPDMatrixCholeskyUpdateFix: Length(Fix)<N");
+            spdmatrixcholeskyupdatefixbuf(a, n, isupper, fix, ref bufr, _params);
+        }
+
+
+        /*************************************************************************
+        Update of Cholesky decomposition: rank-1 update to original A.  "Buffered"
+        version which uses preallocated buffer which is saved  between  subsequent
+        function calls.
+
+        See comments for SPDMatrixCholeskyUpdateAdd1() for more information.
+
+        INPUT PARAMETERS:
+            A       -   upper or lower Cholesky factor.
+                        array with elements [0..N-1, 0..N-1].
+                        Exception is thrown if array size is too small.
+            N       -   size of matrix A, N>0
+            IsUpper -   if IsUpper=True, then A contains  upper  Cholesky  factor;
+                        otherwise A contains a lower one.
+            U       -   array[N], rank-1 update to A: A_mod = A + u*u'
+                        Exception is thrown if array size is too small.
+            BufR    -   possibly preallocated  buffer;  automatically  resized  if
+                        needed. It is recommended to  reuse  this  buffer  if  you
+                        perform a lot of subsequent decompositions.
+
+        OUTPUT PARAMETERS:
+            A       -   updated factorization.  If  IsUpper=True,  then  the  upper
+                        triangle contains matrix U, and the elements below the main
+                        diagonal are not modified. Similarly, if IsUpper = False.
+
+          -- ALGLIB --
+             03.02.2014
+             Sergey Bochkanov
+        *************************************************************************/
+        public static void spdmatrixcholeskyupdateadd1buf(double[,] a,
+            int n,
+            bool isupper,
+            double[] u,
+            ref double[] bufr,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int nz = 0;
+            double cs = 0;
+            double sn = 0;
+            double v = 0;
+            double vv = 0;
+
+            alglib.ap.assert(n>0, "SPDMatrixCholeskyUpdateAdd1Buf: N<=0");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "SPDMatrixCholeskyUpdateAdd1Buf: Rows(A)<N");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "SPDMatrixCholeskyUpdateAdd1Buf: Cols(A)<N");
+            alglib.ap.assert(alglib.ap.len(u)>=n, "SPDMatrixCholeskyUpdateAdd1Buf: Length(U)<N");
+            
+            //
+            // Find index of first non-zero entry in U
+            //
+            nz = n;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(u[i])!=(double)(0) )
+                {
+                    nz = i;
+                    break;
+                }
+            }
+            if( nz==n )
+            {
+                
+                //
+                // Nothing to update
+                //
+                return;
+            }
+            
+            //
+            // If working with upper triangular matrix
+            //
+            if( isupper )
+            {
+                
+                //
+                // Perform a sequence of updates which fix variables one by one.
+                // This approach is different from one which is used when we work
+                // with lower triangular matrix.
+                //
+                apserv.rvectorsetlengthatleast(ref bufr, n, _params);
+                for(j=nz; j<=n-1; j++)
+                {
+                    bufr[j] = u[j];
+                }
+                for(i=nz; i<=n-1; i++)
+                {
+                    if( (double)(bufr[i])!=(double)(0) )
+                    {
+                        rotations.generaterotation(a[i,i], bufr[i], ref cs, ref sn, ref v, _params);
+                        a[i,i] = v;
+                        bufr[i] = 0.0;
+                        for(j=i+1; j<=n-1; j++)
+                        {
+                            v = a[i,j];
+                            vv = bufr[j];
+                            a[i,j] = cs*v+sn*vv;
+                            bufr[j] = -(sn*v)+cs*vv;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Calculate rows of modified Cholesky factor, row-by-row
+                // (updates performed during variable fixing are applied
+                // simultaneously to each row)
+                //
+                apserv.rvectorsetlengthatleast(ref bufr, 3*n, _params);
+                for(j=nz; j<=n-1; j++)
+                {
+                    bufr[j] = u[j];
+                }
+                for(i=nz; i<=n-1; i++)
+                {
+                    
+                    //
+                    // Update all previous updates [Idx+1...I-1] to I-th row
+                    //
+                    vv = bufr[i];
+                    for(j=nz; j<=i-1; j++)
+                    {
+                        cs = bufr[n+2*j+0];
+                        sn = bufr[n+2*j+1];
+                        v = a[i,j];
+                        a[i,j] = cs*v+sn*vv;
+                        vv = -(sn*v)+cs*vv;
+                    }
+                    
+                    //
+                    // generate rotation applied to I-th element of update vector
+                    //
+                    rotations.generaterotation(a[i,i], vv, ref cs, ref sn, ref v, _params);
+                    a[i,i] = v;
+                    bufr[n+2*i+0] = cs;
+                    bufr[n+2*i+1] = sn;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Update of Cholesky  decomposition:  "fixing"  some  variables.  "Buffered"
+        version which uses preallocated buffer which is saved  between  subsequent
+        function calls.
+
+        See comments for SPDMatrixCholeskyUpdateFix() for more information.
+
+        INPUT PARAMETERS:
+            A       -   upper or lower Cholesky factor.
+                        array with elements [0..N-1, 0..N-1].
+                        Exception is thrown if array size is too small.
+            N       -   size of matrix A, N>0
+            IsUpper -   if IsUpper=True, then A contains  upper  Cholesky  factor;
+                        otherwise A contains a lower one.
+            Fix     -   array[N], I-th element is True if I-th  variable  must  be
+                        fixed. Exception is thrown if array size is too small.
+            BufR    -   possibly preallocated  buffer;  automatically  resized  if
+                        needed. It is recommended to  reuse  this  buffer  if  you
+                        perform a lot of subsequent decompositions.
+
+        OUTPUT PARAMETERS:
+            A       -   updated factorization.  If  IsUpper=True,  then  the  upper
+                        triangle contains matrix U, and the elements below the main
+                        diagonal are not modified. Similarly, if IsUpper = False.
+
+          -- ALGLIB --
+             03.02.2014
+             Sergey Bochkanov
+        *************************************************************************/
+        public static void spdmatrixcholeskyupdatefixbuf(double[,] a,
+            int n,
+            bool isupper,
+            bool[] fix,
+            ref double[] bufr,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int nfix = 0;
+            int idx = 0;
+            double cs = 0;
+            double sn = 0;
+            double v = 0;
+            double vv = 0;
+
+            alglib.ap.assert(n>0, "SPDMatrixCholeskyUpdateFixBuf: N<=0");
+            alglib.ap.assert(alglib.ap.rows(a)>=n, "SPDMatrixCholeskyUpdateFixBuf: Rows(A)<N");
+            alglib.ap.assert(alglib.ap.cols(a)>=n, "SPDMatrixCholeskyUpdateFixBuf: Cols(A)<N");
+            alglib.ap.assert(alglib.ap.len(fix)>=n, "SPDMatrixCholeskyUpdateFixBuf: Length(Fix)<N");
+            
+            //
+            // Count number of variables to fix.
+            // Quick exit if NFix=0 or NFix=N
+            //
+            nfix = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( fix[i] )
+                {
+                    apserv.inc(ref nfix, _params);
+                }
+            }
+            if( nfix==0 )
+            {
+                
+                //
+                // Nothing to fix
+                //
+                return;
+            }
+            if( nfix==n )
+            {
+                
+                //
+                // All variables are fixed.
+                // Set A to identity and exit.
+                //
+                if( isupper )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        a[i,i] = 1;
+                        for(j=i+1; j<=n-1; j++)
+                        {
+                            a[i,j] = 0;
+                        }
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=0; j<=i-1; j++)
+                        {
+                            a[i,j] = 0;
+                        }
+                        a[i,i] = 1;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // If working with upper triangular matrix
+            //
+            if( isupper )
+            {
+                
+                //
+                // Perform a sequence of updates which fix variables one by one.
+                // This approach is different from one which is used when we work
+                // with lower triangular matrix.
+                //
+                apserv.rvectorsetlengthatleast(ref bufr, n, _params);
+                for(k=0; k<=n-1; k++)
+                {
+                    if( fix[k] )
+                    {
+                        idx = k;
+                        
+                        //
+                        // Quick exit if it is last variable
+                        //
+                        if( idx==n-1 )
+                        {
+                            for(i=0; i<=idx-1; i++)
+                            {
+                                a[i,idx] = 0.0;
+                            }
+                            a[idx,idx] = 1.0;
+                            continue;
+                        }
+                        
+                        //
+                        // We have Cholesky decomposition of quadratic term in A,
+                        // with upper triangle being stored as given below:
+                        //
+                        //         ( U00 u01 U02 )
+                        //     U = (     u11 u12 )
+                        //         (         U22 )
+                        //
+                        // Here u11 is diagonal element corresponding to variable K. We
+                        // want to fix this variable, and we do so by modifying U as follows:
+                        //
+                        //             ( U00  0  U02 )
+                        //     U_mod = (      1   0  )
+                        //             (         U_m )
+                        //
+                        // with U_m = CHOLESKY [ (U22^T)*U22 + (u12^T)*u12 ]
+                        //
+                        // Of course, we can calculate U_m by calculating (U22^T)*U22 explicitly,
+                        // modifying it and performing Cholesky decomposition of modified matrix.
+                        // However, we can treat it as follows:
+                        // * we already have CHOLESKY[(U22^T)*U22], which is equal to U22
+                        // * we have rank-1 update (u12^T)*u12 applied to (U22^T)*U22
+                        // * thus, we can calculate updated Cholesky with O(N^2) algorithm
+                        //   instead of O(N^3) one
+                        //
+                        for(j=idx+1; j<=n-1; j++)
+                        {
+                            bufr[j] = a[idx,j];
+                        }
+                        for(i=0; i<=idx-1; i++)
+                        {
+                            a[i,idx] = 0.0;
+                        }
+                        a[idx,idx] = 1.0;
+                        for(i=idx+1; i<=n-1; i++)
+                        {
+                            a[idx,i] = 0.0;
+                        }
+                        for(i=idx+1; i<=n-1; i++)
+                        {
+                            if( (double)(bufr[i])!=(double)(0) )
+                            {
+                                rotations.generaterotation(a[i,i], bufr[i], ref cs, ref sn, ref v, _params);
+                                a[i,i] = v;
+                                bufr[i] = 0.0;
+                                for(j=i+1; j<=n-1; j++)
+                                {
+                                    v = a[i,j];
+                                    vv = bufr[j];
+                                    a[i,j] = cs*v+sn*vv;
+                                    bufr[j] = -(sn*v)+cs*vv;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Calculate rows of modified Cholesky factor, row-by-row
+                // (updates performed during variable fixing are applied
+                // simultaneously to each row)
+                //
+                apserv.rvectorsetlengthatleast(ref bufr, 3*n, _params);
+                for(k=0; k<=n-1; k++)
+                {
+                    if( fix[k] )
+                    {
+                        idx = k;
+                        
+                        //
+                        // Quick exit if it is last variable
+                        //
+                        if( idx==n-1 )
+                        {
+                            for(i=0; i<=idx-1; i++)
+                            {
+                                a[idx,i] = 0.0;
+                            }
+                            a[idx,idx] = 1.0;
+                            continue;
+                        }
+                        
+                        //
+                        // store column to buffer and clear row/column of A
+                        //
+                        for(j=idx+1; j<=n-1; j++)
+                        {
+                            bufr[j] = a[j,idx];
+                        }
+                        for(i=0; i<=idx-1; i++)
+                        {
+                            a[idx,i] = 0.0;
+                        }
+                        a[idx,idx] = 1.0;
+                        for(i=idx+1; i<=n-1; i++)
+                        {
+                            a[i,idx] = 0.0;
+                        }
+                        
+                        //
+                        // Apply update to rows of A
+                        //
+                        for(i=idx+1; i<=n-1; i++)
+                        {
+                            
+                            //
+                            // Update all previous updates [Idx+1...I-1] to I-th row
+                            //
+                            vv = bufr[i];
+                            for(j=idx+1; j<=i-1; j++)
+                            {
+                                cs = bufr[n+2*j+0];
+                                sn = bufr[n+2*j+1];
+                                v = a[i,j];
+                                a[i,j] = cs*v+sn*vv;
+                                vv = -(sn*v)+cs*vv;
+                            }
+                            
+                            //
+                            // generate rotation applied to I-th element of update vector
+                            //
+                            rotations.generaterotation(a[i,i], vv, ref cs, ref sn, ref v, _params);
+                            a[i,i] = v;
+                            bufr[n+2*i+0] = cs;
+                            bufr[n+2*i+1] = sn;
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Sparse LU decomposition with column pivoting for sparsity and row pivoting
+        for stability. Input must be square sparse matrix stored in CRS format.
+
+        The algorithm  computes  LU  decomposition  of  a  general  square  matrix
+        (rectangular ones are not supported). The result  of  an  algorithm  is  a
+        representation of A as A = P*L*U*Q, where:
+        * L is lower unitriangular matrix
+        * U is upper triangular matrix
+        * P = P0*P1*...*PK, K=N-1, Pi - permutation matrix for I and P[I]
+        * Q = QK*...*Q1*Q0, K=N-1, Qi - permutation matrix for I and Q[I]
+            
+        This function pivots columns for higher sparsity, and then pivots rows for
+        stability (larger element at the diagonal).
+
+        INPUT PARAMETERS:
+            A       -   sparse NxN matrix in CRS format. An exception is generated
+                        if matrix is non-CRS or non-square.
+            PivotType-  pivoting strategy:
+                        * 0 for best pivoting available (2 in current version)
+                        * 1 for row-only pivoting (NOT RECOMMENDED)
+                        * 2 for complete pivoting which produces most sparse outputs
+
+        OUTPUT PARAMETERS:
+            A       -   the result of factorization, matrices L and U stored in
+                        compact form using CRS sparse storage format:
+                        * lower unitriangular L is stored strictly under main diagonal
+                        * upper triangilar U is stored ON and ABOVE main diagonal
+            P       -   row permutation matrix in compact form, array[N]
+            Q       -   col permutation matrix in compact form, array[N]
+            
+        This function always succeeds, i.e. it ALWAYS returns valid factorization,
+        but for your convenience it also returns  boolean  value  which  helps  to
+        detect symbolically degenerate matrices:
+        * function returns TRUE, if the matrix was factorized AND symbolically
+          non-degenerate
+        * function returns FALSE, if the matrix was factorized but U has strictly
+          zero elements at the diagonal (the factorization is returned anyway).
+
+
+          -- ALGLIB routine --
+             03.09.2018
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool sparselu(sparse.sparsematrix a,
+            int pivottype,
+            ref int[] p,
+            ref int[] q,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            sptrf.sluv2buffer buf2 = new sptrf.sluv2buffer();
+
+            p = new int[0];
+            q = new int[0];
+
+            alglib.ap.assert((pivottype==0 || pivottype==1) || pivottype==2, "SparseLU: unexpected pivot type");
+            alglib.ap.assert(sparse.sparseiscrs(a, _params), "SparseLU: A is not stored in CRS format");
+            alglib.ap.assert(sparse.sparsegetnrows(a, _params)==sparse.sparsegetncols(a, _params), "SparseLU: non-square A");
+            result = sptrf.sptrflu(a, pivottype, ref p, ref q, buf2, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Sparse Cholesky decomposition for skyline matrixm using in-place algorithm
+        without allocating additional storage.
+
+        The algorithm computes Cholesky decomposition  of  a  symmetric  positive-
+        definite sparse matrix. The result of an algorithm is a representation  of
+        A as A=U^T*U or A=L*L^T
+
+        This  function  is  a  more  efficient alternative to general, but  slower
+        SparseCholeskyX(), because it does not  create  temporary  copies  of  the
+        target. It performs factorization in-place, which gives  best  performance
+        on low-profile matrices. Its drawback, however, is that it can not perform
+        profile-reducing permutation of input matrix.
+
+        INPUT PARAMETERS:
+            A       -   sparse matrix in skyline storage (SKS) format.
+            N       -   size of matrix A (can be smaller than actual size of A)
+            IsUpper -   if IsUpper=True, then factorization is performed on  upper
+                        triangle. Another triangle is ignored (it may contant some
+                        data, but it is not changed).
+            
+
+        OUTPUT PARAMETERS:
+            A       -   the result of factorization, stored in SKS. If IsUpper=True,
+                        then the upper  triangle  contains  matrix  U,  such  that
+                        A = U^T*U. Lower triangle is not changed.
+                        Similarly, if IsUpper = False. In this case L is returned,
+                        and we have A = L*(L^T).
+                        Note that THIS function does not  perform  permutation  of
+                        rows to reduce bandwidth.
+
+        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.
+
+        NOTE: for  performance  reasons  this  function  does NOT check that input
+              matrix  includes  only  finite  values. It is your responsibility to
+              make sure that there are no infinite or NAN values in the matrix.
+
+          -- ALGLIB routine --
+             16.01.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool sparsecholeskyskyline(sparse.sparsematrix a,
+            int n,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int jnz = 0;
+            int jnza = 0;
+            int jnzl = 0;
+            double v = 0;
+            double vv = 0;
+            double a12 = 0;
+            int nready = 0;
+            int nadd = 0;
+            int banda = 0;
+            int offsa = 0;
+            int offsl = 0;
+
+            alglib.ap.assert(n>=0, "SparseCholeskySkyline: N<0");
+            alglib.ap.assert(sparse.sparsegetnrows(a, _params)>=n, "SparseCholeskySkyline: rows(A)<N");
+            alglib.ap.assert(sparse.sparsegetncols(a, _params)>=n, "SparseCholeskySkyline: cols(A)<N");
+            alglib.ap.assert(sparse.sparseissks(a, _params), "SparseCholeskySkyline: A is not stored in SKS format");
+            result = false;
+            
+            //
+            // transpose if needed
+            //
+            if( isupper )
+            {
+                sparse.sparsetransposesks(a, _params);
+            }
+            
+            //
+            // Perform Cholesky decomposition:
+            // * we assume than leading NReady*NReady submatrix is done
+            // * having Cholesky decomposition of NReady*NReady submatrix we
+            //   obtain decomposition of larger (NReady+NAdd)*(NReady+NAdd) one.
+            //
+            // Here is algorithm. At the start we have
+            //
+            //     (      |   )
+            //     (  L   |   )
+            // S = (      |   )
+            //     (----------)
+            //     (  A   | B )
+            //
+            // with L being already computed Cholesky factor, A and B being
+            // unprocessed parts of the matrix. Of course, L/A/B are stored
+            // in SKS format.
+            //
+            // Then, we calculate A1:=(inv(L)*A')' and replace A with A1.
+            // Then, we calculate B1:=B-A1*A1'     and replace B with B1
+            //
+            // Finally, we calculate small NAdd*NAdd Cholesky of B1 with
+            // dense solver. Now, L/A1/B1 are Cholesky decomposition of the
+            // larger (NReady+NAdd)*(NReady+NAdd) matrix.
+            //
+            nready = 0;
+            nadd = 1;
+            while( nready<n )
+            {
+                alglib.ap.assert(nadd==1, "SkylineCholesky: internal error");
+                
+                //
+                // Calculate A1:=(inv(L)*A')'
+                //
+                // Elements are calculated row by row (example below is given
+                // for NAdd=1):
+                // * first, we solve L[0,0]*A1[0]=A[0]
+                // * then, we solve  L[1,0]*A1[0]+L[1,1]*A1[1]=A[1]
+                // * then, we move to next row and so on
+                // * during calculation of A1 we update A12 - squared norm of A1
+                //
+                // We extensively use sparsity of both A/A1 and L:
+                // * first, equations from 0 to BANDWIDTH(A1)-1 are completely zero
+                // * second, for I>=BANDWIDTH(A1), I-th equation is reduced from
+                //     L[I,0]*A1[0] + L[I,1]*A1[1] + ... + L[I,I]*A1[I] = A[I]
+                //   to
+                //     L[I,JNZ]*A1[JNZ] + ... + L[I,I]*A1[I] = A[I]
+                //   where JNZ = max(NReady-BANDWIDTH(A1),I-BANDWIDTH(L[i]))
+                //   (JNZ is an index of the firts column where both A and L become
+                //   nonzero).
+                //
+                // NOTE: we rely on details of SparseMatrix internal storage format.
+                //       This is allowed by SparseMatrix specification.
+                //
+                a12 = 0.0;
+                if( a.didx[nready]>0 )
+                {
+                    banda = a.didx[nready];
+                    for(i=nready-banda; i<=nready-1; i++)
+                    {
+                        
+                        //
+                        // Elements of A1[0:I-1] were computed:
+                        // * A1[0:NReady-BandA-1] are zero (sparse)
+                        // * A1[NReady-BandA:I-1] replaced corresponding elements of A
+                        //
+                        // Now it is time to get I-th one.
+                        //
+                        // First, we calculate:
+                        // * JNZA  - index of the first column where A become nonzero
+                        // * JNZL  - index of the first column where L become nonzero
+                        // * JNZ   - index of the first column where both A and L become nonzero
+                        // * OffsA - offset of A[JNZ] in A.Vals
+                        // * OffsL - offset of L[I,JNZ] in A.Vals
+                        //
+                        // Then, we solve SUM(A1[j]*L[I,j],j=JNZ..I-1) + A1[I]*L[I,I] = A[I],
+                        // with A1[JNZ..I-1] already known, and A1[I] unknown.
+                        //
+                        jnza = nready-banda;
+                        jnzl = i-a.didx[i];
+                        jnz = Math.Max(jnza, jnzl);
+                        offsa = a.ridx[nready]+(jnz-jnza);
+                        offsl = a.ridx[i]+(jnz-jnzl);
+                        v = 0.0;
+                        k = i-1-jnz;
+                        for(j=0; j<=k; j++)
+                        {
+                            v = v+a.vals[offsa+j]*a.vals[offsl+j];
+                        }
+                        vv = (a.vals[offsa+k+1]-v)/a.vals[offsl+k+1];
+                        a.vals[offsa+k+1] = vv;
+                        a12 = a12+vv*vv;
+                    }
+                }
+                
+                //
+                // Calculate CHOLESKY(B-A1*A1')
+                //
+                offsa = a.ridx[nready]+a.didx[nready];
+                v = a.vals[offsa];
+                if( (double)(v)<=(double)(a12) )
+                {
+                    result = false;
+                    return result;
+                }
+                a.vals[offsa] = Math.Sqrt(v-a12);
+                
+                //
+                // Increase size of the updated matrix
+                //
+                apserv.inc(ref nready, _params);
+            }
+            
+            //
+            // transpose if needed
+            //
+            if( isupper )
+            {
+                sparse.sparsetransposesks(a, _params);
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Sparse Cholesky decomposition: "expert" function.
+
+        The algorithm computes Cholesky decomposition  of  a  symmetric  positive-
+        definite sparse matrix. The result is representation of A  as  A=U^T*U  or
+        A=L*L^T
+
+        Triangular factor L or U is written to separate SparseMatrix structure. If
+        output buffer already contrains enough memory to store L/U, this memory is
+        reused.
+
+        INPUT PARAMETERS:
+            A       -   upper or lower triangle of sparse matrix.
+                        Matrix can be in any sparse storage format.
+            N       -   size of matrix A (can be smaller than actual size of A)
+            IsUpper -   if IsUpper=True, then A contains an upper triangle of
+                        a symmetric matrix, otherwise A contains a lower one.
+                        Another triangle is ignored.
+            P0, P1  -   integer arrays:
+                        * for Ordering=-3  -  user-supplied permutation  of  rows/
+                          columns, which complies  to  requirements stated  in the
+                          "OUTPUT PARAMETERS" section.  Both  P0 and  P1  must  be
+                          initialized by user.
+                        * for other values of  Ordering  -  possibly  preallocated
+                          buffer,  which   is   filled   by  internally  generated
+                          permutation. Automatically resized if its  size  is  too
+                          small to store data.
+            Ordering-   sparse matrix reordering algorithm which is used to reduce
+                        fill-in amount:
+                        * -3    use ordering supplied by user in P0/P1
+                        * -2    use random ordering
+                        * -1    use original order
+                        * 0     use best algorithm implemented so far
+                        If input matrix is  given  in  SKS  format,  factorization
+                        function ignores Ordering and uses original order  of  the
+                        columns. The idea is that if you already store  matrix  in
+                        SKS format, it is better not to perform costly reordering.
+            Algo    -   type of algorithm which is used during factorization:
+                        * 0     use best  algorithm  (for  SKS  input  or   output
+                                matrices Algo=2 is used; otherwise Algo=1 is used)
+                        * 1     use CRS-based algorithm
+                        * 2     use skyline-based factorization algorithm.
+                                This algorithm is a  fastest  one  for low-profile
+                                matrices,  but  requires  too  much of memory  for
+                                matrices with large bandwidth.
+            Fmt     -   desired storage format  of  the  output,  as  returned  by
+                        SparseGetMatrixType() function:
+                        * 0 for hash-based storage
+                        * 1 for CRS
+                        * 2 for SKS
+                        If you do not know what format to choose, use 1 (CRS).
+            Buf     -   SparseBuffers structure which is used to store temporaries.
+                        This function may reuse previously allocated  storage,  so
+                        if you perform repeated factorizations it is beneficial to
+                        reuse Buf.
+            C       -   SparseMatrix structure  which  can  be  just  some  random
+                        garbage. In  case  in  contains  enough  memory  to  store
+                        triangular factors, this memory will be reused. Othwerwise,
+                        algorithm will automatically allocate enough memory.
+            
+
+        OUTPUT PARAMETERS:
+            C       -   the result of factorization, stored in desired format.  If
+                        IsUpper=True, then the upper triangle  contains  matrix U,
+                        such  that  (P'*A*P) = U^T*U,  where  P  is  a permutation
+                        matrix (see below). The elements below the  main  diagonal
+                        are zero.
+                        Similarly, if IsUpper = False. In this case L is returned,
+                        and we have (P'*A*P) = L*(L^T).
+            P0      -   permutation  (according   to   Ordering  parameter)  which
+                        minimizes amount of fill-in:
+                        * P0 is array[N]
+                        * permutation is applied to A before  factorization  takes
+                          place, i.e. we have U'*U = L*L' = P'*A*P
+                        * P0[k]=j means that column/row j of A  is  moved  to k-th
+                          position before starting factorization.
+            P1      -   permutation P in another format, array[N]:
+                        * P1[k]=j means that k-th column/row of A is moved to j-th
+                          position
+
+        RESULT:
+            If  the  matrix  is  positive-definite,  the  function  returns  True.
+            Otherwise, the function returns False. Contents of C is not determined
+            in such case.
+
+        NOTE: for  performance  reasons  this  function  does NOT check that input
+              matrix  includes  only  finite  values. It is your responsibility to
+              make sure that there are no infinite or NAN values in the matrix.
+
+          -- ALGLIB routine --
+             16.01.2014
+             Bochkanov Sergey
+        *************************************************************************/
+        public static bool sparsecholeskyx(sparse.sparsematrix a,
+            int n,
+            bool isupper,
+            ref int[] p0,
+            ref int[] p1,
+            int ordering,
+            int algo,
+            int fmt,
+            sparse.sparsebuffers buf,
+            sparse.sparsematrix c,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int t0 = 0;
+            int t1 = 0;
+            double v = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+
+            alglib.ap.assert(n>=0, "SparseMatrixCholeskyBuf: N<0");
+            alglib.ap.assert(sparse.sparsegetnrows(a, _params)>=n, "SparseMatrixCholeskyBuf: rows(A)<N");
+            alglib.ap.assert(sparse.sparsegetncols(a, _params)>=n, "SparseMatrixCholeskyBuf: cols(A)<N");
+            alglib.ap.assert(ordering>=-3 && ordering<=0, "SparseMatrixCholeskyBuf: invalid Ordering parameter");
+            alglib.ap.assert(algo>=0 && algo<=2, "SparseMatrixCholeskyBuf: invalid Algo parameter");
+            hqrnd.hqrndrandomize(rs, _params);
+            
+            //
+            // Perform some quick checks.
+            // Because sparse matrices are expensive data structures, these
+            // checks are better to perform during early stages of the factorization.
+            //
+            result = false;
+            if( n<1 )
+            {
+                return result;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(sparse.sparsegetdiagonal(a, i, _params))<=(double)(0) )
+                {
+                    return result;
+                }
+            }
+            
+            //
+            // First, determine appropriate ordering:
+            // * for SKS inputs, Ordering=-1 is automatically chosen (overrides user settings)
+            //
+            if( ordering==0 )
+            {
+                ordering = -1;
+            }
+            if( sparse.sparseissks(a, _params) )
+            {
+                ordering = -1;
+            }
+            if( ordering==-3 )
+            {
+                
+                //
+                // User-supplied ordering.
+                // Check its correctness.
+                //
+                alglib.ap.assert(alglib.ap.len(p0)>=n, "SparseCholeskyX: user-supplied permutation is too short");
+                alglib.ap.assert(alglib.ap.len(p1)>=n, "SparseCholeskyX: user-supplied permutation is too short");
+                for(i=0; i<=n-1; i++)
+                {
+                    alglib.ap.assert(p0[i]>=0 && p0[i]<n, "SparseCholeskyX: user-supplied permutation includes values outside of [0,N)");
+                    alglib.ap.assert(p1[i]>=0 && p1[i]<n, "SparseCholeskyX: user-supplied permutation includes values outside of [0,N)");
+                    alglib.ap.assert(p1[p0[i]]==i, "SparseCholeskyX: user-supplied permutation is inconsistent - P1 is not inverse of P0");
+                }
+            }
+            if( ordering==-2 )
+            {
+                
+                //
+                // Use random ordering
+                //
+                apserv.ivectorsetlengthatleast(ref p0, n, _params);
+                apserv.ivectorsetlengthatleast(ref p1, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    p0[i] = i;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    j = i+hqrnd.hqrnduniformi(rs, n-i, _params);
+                    if( j!=i )
+                    {
+                        k = p0[i];
+                        p0[i] = p0[j];
+                        p0[j] = k;
+                    }
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    p1[p0[i]] = i;
+                }
+            }
+            if( ordering==-1 )
+            {
+                
+                //
+                // Use initial ordering
+                //
+                apserv.ivectorsetlengthatleast(ref p0, n, _params);
+                apserv.ivectorsetlengthatleast(ref p1, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    p0[i] = i;
+                    p1[i] = i;
+                }
+            }
+            
+            //
+            // Determine algorithm to use:
+            // * for SKS input or output - use SKS solver (overrides user settings)
+            // * default is to use Algo=1
+            //
+            if( algo==0 )
+            {
+                algo = 1;
+            }
+            if( sparse.sparseissks(a, _params) || fmt==2 )
+            {
+                algo = 2;
+            }
+            algo = 2;
+            if( algo==2 )
+            {
+                
+                //
+                // Skyline Cholesky with non-skyline output.
+                //
+                // Call CholeskyX() recursively with Buf.S as output matrix,
+                // then perform conversion from SKS to desired format. We can
+                // use Buf.S in reccurrent call because SKS-to-SKS CholeskyX()
+                // does not uses this field.
+                //
+                if( fmt!=2 )
+                {
+                    result = sparsecholeskyx(a, n, isupper, ref p0, ref p1, -3, algo, 2, buf, buf.s, _params);
+                    if( result )
+                    {
+                        sparse.sparsecopytobuf(buf.s, fmt, c, _params);
+                    }
+                    return result;
+                }
+                
+                //
+                // Skyline Cholesky with skyline output
+                //
+                if( sparse.sparseissks(a, _params) && ordering==-1 )
+                {
+                    
+                    //
+                    // Non-permuted skyline matrix.
+                    //
+                    // Quickly copy matrix to output buffer without permutation.
+                    //
+                    // NOTE: Buf.D is used as dummy vector filled with zeros.
+                    //
+                    apserv.ivectorsetlengthatleast(ref buf.d, n, _params);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        buf.d[i] = 0;
+                    }
+                    if( isupper )
+                    {
+                        
+                        //
+                        // Create strictly upper-triangular matrix,
+                        // copy upper triangle of input.
+                        //
+                        sparse.sparsecreatesksbuf(n, n, buf.d, a.uidx, c, _params);
+                        for(i=0; i<=n-1; i++)
+                        {
+                            t0 = a.ridx[i+1]-a.uidx[i]-1;
+                            t1 = a.ridx[i+1]-1;
+                            k = c.ridx[i+1]-c.uidx[i]-1;
+                            for(j=t0; j<=t1; j++)
+                            {
+                                c.vals[k] = a.vals[j];
+                                k = k+1;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Create strictly lower-triangular matrix,
+                        // copy lower triangle of input.
+                        //
+                        sparse.sparsecreatesksbuf(n, n, a.didx, buf.d, c, _params);
+                        for(i=0; i<=n-1; i++)
+                        {
+                            t0 = a.ridx[i];
+                            t1 = a.ridx[i]+a.didx[i];
+                            k = c.ridx[i];
+                            for(j=t0; j<=t1; j++)
+                            {
+                                c.vals[k] = a.vals[j];
+                                k = k+1;
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Non-identity permutations OR non-skyline input:
+                    // * investigate profile of permuted A
+                    // * create skyline matrix in output buffer
+                    // * copy input with permutation
+                    //
+                    apserv.ivectorsetlengthatleast(ref buf.d, n, _params);
+                    apserv.ivectorsetlengthatleast(ref buf.u, n, _params);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        buf.d[i] = 0;
+                        buf.u[i] = 0;
+                    }
+                    t0 = 0;
+                    t1 = 0;
+                    while( sparse.sparseenumerate(a, ref t0, ref t1, ref i, ref j, ref v, _params) )
+                    {
+                        if( (isupper && j>=i) || (!isupper && j<=i) )
+                        {
+                            i = p1[i];
+                            j = p1[j];
+                            if( (j<i && isupper) || (j>i && !isupper) )
+                            {
+                                apserv.swapi(ref i, ref j, _params);
+                            }
+                            if( i>j )
+                            {
+                                buf.d[i] = Math.Max(buf.d[i], i-j);
+                            }
+                            else
+                            {
+                                buf.u[j] = Math.Max(buf.u[j], j-i);
+                            }
+                        }
+                    }
+                    sparse.sparsecreatesksbuf(n, n, buf.d, buf.u, c, _params);
+                    t0 = 0;
+                    t1 = 0;
+                    while( sparse.sparseenumerate(a, ref t0, ref t1, ref i, ref j, ref v, _params) )
+                    {
+                        if( (isupper && j>=i) || (!isupper && j<=i) )
+                        {
+                            i = p1[i];
+                            j = p1[j];
+                            if( (j<i && isupper) || (j>i && !isupper) )
+                            {
+                                apserv.swapi(ref j, ref i, _params);
+                            }
+                            sparse.sparserewriteexisting(c, i, j, v, _params);
+                        }
+                    }
+                }
+                result = sparsecholeskyskyline(c, n, isupper, _params);
+                return result;
+            }
+            alglib.ap.assert(false, "SparseCholeskyX: internal error - unexpected algorithm");
+            return result;
+        }
+
+
+        public static void rmatrixlup(ref double[,] a,
+            int m,
+            int n,
+            ref int[] pivots,
+            alglib.xparams _params)
+        {
+            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)];
+            dlu.rmatrixluprec(ref a, 0, m, n, ref pivots, ref tmp, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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)];
+            dlu.cmatrixluprec(ref a, 0, m, n, ref pivots, ref tmp, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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_];
+                    }
+                }
+            }
+            dlu.rmatrixplurec(ref a, 0, m, n, ref pivots, ref tmp, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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_];
+                    }
+                }
+            }
+            dlu.cmatrixplurec(ref a, 0, m, n, ref pivots, ref tmp, _params);
+            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_];
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Advanced interface for SPDMatrixCholesky, performs no temporary allocations.
+
+        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,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n1 = 0;
+            int n2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+
+            tsa = apserv.matrixtilesizea(_params);
+            tsb = apserv.matrixtilesizeb(_params);
+            
+            //
+            // Allocate temporaries
+            //
+            if( alglib.ap.len(tmp)<2*n )
+            {
+                tmp = new double[2*n];
+            }
+            
+            //
+            // Basecases
+            //
+            if( n<1 )
+            {
+                result = false;
+                return result;
+            }
+            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<=tsb )
+            {
+                if( ablasmkl.spdmatrixcholeskymkl(a, offs, n, isupper, ref result, _params) )
+                {
+                    return result;
+                }
+            }
+            if( n<=tsa )
+            {
+                result = spdmatrixcholesky2(a, offs, n, isupper, tmp, _params);
+                return result;
+            }
+            
+            //
+            // Split task into smaller ones
+            //
+            if( n>tsb )
+            {
+                
+                //
+                // Split leading B-sized block from the beginning (block-matrix approach)
+                //
+                n1 = tsb;
+                n2 = n-n1;
+            }
+            else
+            {
+                
+                //
+                // Smaller than B-size, perform cache-oblivious split
+                //
+                apserv.tiledsplit(n, tsa, ref n1, ref n2, _params);
+            }
+            result = spdmatrixcholeskyrec(ref a, offs, n1, isupper, ref tmp, _params);
+            if( !result )
+            {
+                return result;
+            }
+            if( n2>0 )
+            {
+                if( isupper )
+                {
+                    ablas.rmatrixlefttrsm(n1, n2, a, offs, offs, isupper, false, 1, a, offs, offs+n1, _params);
+                    ablas.rmatrixsyrk(n2, n1, -1.0, a, offs, offs+n1, 1, 1.0, a, offs+n1, offs+n1, isupper, _params);
+                }
+                else
+                {
+                    ablas.rmatrixrighttrsm(n2, n1, a, offs, offs, isupper, false, 1, a, offs+n1, offs, _params);
+                    ablas.rmatrixsyrk(n2, n1, -1.0, a, offs+n1, offs, 0, 1.0, a, offs+n1, offs+n1, isupper, _params);
+                }
+                result = spdmatrixcholeskyrec(ref a, offs+n1, n2, isupper, ref tmp, _params);
+                if( !result )
+                {
+                    return result;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n1 = 0;
+            int n2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+
+            tsa = apserv.matrixtilesizea(_params)/2;
+            tsb = apserv.matrixtilesizeb(_params);
+            
+            //
+            // check N
+            //
+            if( n<1 )
+            {
+                result = false;
+                return result;
+            }
+            
+            //
+            // Prepare buffer
+            //
+            if( alglib.ap.len(tmp)<2*n )
+            {
+                tmp = new complex[2*n];
+            }
+            
+            //
+            // Basecases
+            //
+            // NOTE: we do not use MKL for basecases because their price is only
+            //       minor part of overall running time for N>256.
+            //
+            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<=tsa )
+            {
+                result = hpdmatrixcholesky2(ref a, offs, n, isupper, ref tmp, _params);
+                return result;
+            }
+            
+            //
+            // Split task into smaller ones
+            //
+            if( n>tsb )
+            {
+                
+                //
+                // Split leading B-sized block from the beginning (block-matrix approach)
+                //
+                n1 = tsb;
+                n2 = n-n1;
+            }
+            else
+            {
+                
+                //
+                // Smaller than B-size, perform cache-oblivious split
+                //
+                apserv.tiledsplit(n, tsa, ref n1, ref n2, _params);
+            }
+            result = hpdmatrixcholeskyrec(ref a, offs, n1, isupper, ref tmp, _params);
+            if( !result )
+            {
+                return result;
+            }
+            if( n2>0 )
+            {
+                if( isupper )
+                {
+                    ablas.cmatrixlefttrsm(n1, n2, a, offs, offs, isupper, false, 2, a, offs, offs+n1, _params);
+                    ablas.cmatrixherk(n2, n1, -1.0, a, offs, offs+n1, 2, 1.0, a, offs+n1, offs+n1, isupper, _params);
+                }
+                else
+                {
+                    ablas.cmatrixrighttrsm(n2, n1, a, offs, offs, isupper, false, 2, a, offs+n1, offs, _params);
+                    ablas.cmatrixherk(n2, n1, -1.0, a, offs+n1, offs, 0, 1.0, a, offs+n1, offs+n1, isupper, _params);
+                }
+                result = hpdmatrixcholeskyrec(ref a, offs+n1, n2, isupper, ref tmp, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                            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 )
+                    {
+                        r = 1/ajj;
+                        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, _params);
+                            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])*r;
+                            }
+                        }
+                        else
+                        {
+                            for(i=0; i<=n-j-2; i++)
+                            {
+                                aaa[offs+j+1+i,offs+j] = aaa[offs+j+1+i,offs+j]*r;
+                            }
+                        }
+                    }
+                }
+            }
+            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(double[,] aaa,
+            int offs,
+            int n,
+            bool isupper,
+            double[] tmp,
+            alglib.xparams _params)
+        {
+            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, tmp, n, _params);
+                            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 )
+                    {
+                        r = 1/ajj;
+                        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, tmp, n, _params);
+                            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])*r;
+                            }
+                        }
+                        else
+                        {
+                            for(i=0; i<=n-j-2; i++)
+                            {
+                                aaa[offs+j+1+i,offs+j] = aaa[offs+j+1+i,offs+j]*r;
+                            }
+                        }
+                    }
+                }
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            rmatrixrcondluinternal(a, n, true, true, nrm, ref v, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            rmatrixrcondluinternal(a, n, false, true, nrm, ref v, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params) )
+            {
+                spdmatrixrcondcholeskyinternal(a, n, isupper, true, nrm, ref v, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params) )
+            {
+                hpdmatrixrcondcholeskyinternal(a, n, isupper, true, nrm, ref v, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            cmatrixrcondluinternal(a, n, true, true, nrm, ref v, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            cmatrixrcondluinternal(a, n, false, true, nrm, ref v, _params);
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double v = 0;
+
+            rmatrixrcondluinternal(lua, n, true, false, 0, ref v, _params);
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double v = 0;
+
+            rmatrixrcondluinternal(lua, n, false, false, 0, ref v, _params);
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double v = 0;
+
+            spdmatrixrcondcholeskyinternal(a, n, isupper, false, 0, ref v, _params);
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double v = 0;
+
+            hpdmatrixrcondcholeskyinternal(a, n, isupper, false, 0, ref v, _params);
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double v = 0;
+
+            alglib.ap.assert(n>=1, "CMatrixLURCond1: N<1!");
+            cmatrixrcondluinternal(lua, n, true, false, 0.0, ref v, _params);
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double v = 0;
+
+            alglib.ap.assert(n>=1, "CMatrixLURCondInf: N<1!");
+            cmatrixrcondluinternal(lua, n, false, false, 0.0, ref v, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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(alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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;
+
+            rc = 0;
+
+            
+            //
+            // RC=0 if something happens
+            //
+            rc = 0;
+            
+            //
+            // init
+            //
+            if( onenorm )
+            {
+                kase1 = 1;
+            }
+            else
+            {
+                kase1 = 2;
+            }
+            iwork = new int[n+1];
+            tmp = new double[n];
+            
+            //
+            // prepare parameters for triangular solver
+            //
+            maxgrowth = 1/rcondthreshold(_params);
+            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, _params);
+                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, _params) )
+                    {
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // multiply by inv(A')
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(a, s, n, ref ex, isupper, 1, isunit, maxgrowth, _params) )
+                    {
+                        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(_params)) )
+                {
+                    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,
+            alglib.xparams _params)
+        {
+            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(_params);
+            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, _params);
+                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, _params) )
+                    {
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // multiply by inv(A')
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(a, s, n, ref ex, isupper, 2, isunit, maxgrowth, _params) )
+                    {
+                        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(_params)) )
+                {
+                    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,
+            alglib.xparams _params)
+        {
+            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(_params);
+            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, _params);
+                    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, _params);
+                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, _params) )
+                    {
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(U).
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 0, false, maxgrowth, _params) )
+                    {
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Multiply by inv(L).
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 0, false, maxgrowth, _params) )
+                    {
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(L').
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 1, false, maxgrowth, _params) )
+                    {
+                        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(_params)) )
+                {
+                    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,
+            alglib.xparams _params)
+        {
+            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(_params);
+            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, _params);
+                    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, _params);
+                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, _params) )
+                    {
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(U).
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 0, false, maxgrowth, _params) )
+                    {
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Multiply by inv(L).
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 0, false, maxgrowth, _params) )
+                    {
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(L').
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(cha, sa, n, ref ex, isupper, 2, false, maxgrowth, _params) )
+                    {
+                        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(_params)) )
+                {
+                    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,
+            alglib.xparams _params)
+        {
+            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 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;
+            munit = true;
+            iwork = new int[n+1];
+            tmp = new double[n];
+            
+            //
+            // prepare parameters for triangular solver
+            //
+            maxgrowth = 1/rcondthreshold(_params);
+            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, _params);
+                    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, _params);
+                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, _params) )
+                    {
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(U).
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(lua, su, n, ref ex, mupper, 0, !munit, maxgrowth, _params) )
+                    {
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Multiply by inv(U').
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(lua, su, n, ref ex, mupper, 1, !munit, maxgrowth, _params) )
+                    {
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(L').
+                    //
+                    if( !safesolve.rmatrixscaledtrsafesolve(lua, sl, n, ref ex, !mupper, 1, munit, maxgrowth, _params) )
+                    {
+                        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(_params)) )
+                {
+                    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,
+            alglib.xparams _params)
+        {
+            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(_params);
+            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, _params);
+                    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, _params);
+                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, _params) )
+                    {
+                        rc = 0;
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(U).
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(lua, su, n, ref ex, true, 0, false, maxgrowth, _params) )
+                    {
+                        rc = 0;
+                        return;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Multiply by inv(U').
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(lua, su, n, ref ex, true, 2, false, maxgrowth, _params) )
+                    {
+                        rc = 0;
+                        return;
+                    }
+                    
+                    //
+                    // Multiply by inv(L').
+                    //
+                    if( !safesolve.cmatrixscaledtrsafesolve(lua, sl, n, ref ex, false, 2, true, maxgrowth, _params) )
+                    {
+                        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(_params)) )
+                {
+                    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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                return;
+            }
+            internalcomplexrcondloadall(ref isave, ref rsave, ref i, ref iter, ref j, ref jlast, ref jump, ref absxi, ref altsgn, ref estold, ref temp, _params);
+            
+            //
+            // 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, _params);
+                    return;
+                }
+                est = internalcomplexrcondscsum1(x, n, _params);
+                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, _params);
+                return;
+            }
+            
+            //
+            // ENTRY   (JUMP = 2)
+            // FIRST ITERATION.  X HAS BEEN OVERWRITTEN BY CTRANS(A)*X.
+            //
+            if( jump==2 )
+            {
+                j = internalcomplexrcondicmax1(x, n, _params);
+                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, _params);
+                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, _params);
+                
+                //
+                // 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, _params);
+                    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, _params);
+                return;
+            }
+            
+            //
+            // ENTRY   (JUMP = 4)
+            // X HAS BEEN OVERWRITTEN BY CTRANS(A)*X.
+            //
+            if( jump==4 )
+            {
+                jlast = j;
+                j = internalcomplexrcondicmax1(x, n, _params);
+                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, _params);
+                    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, _params);
+                return;
+            }
+            
+            //
+            // ENTRY   (JUMP = 5)
+            // X HAS BEEN OVERWRITTEN BY A*X.
+            //
+            if( jump==5 )
+            {
+                temp = 2*(internalcomplexrcondscsum1(x, n, _params)/(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, _params);
+                return;
+            }
+        }
+
+
+        private static double internalcomplexrcondscsum1(complex[] x,
+            int n,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 : apobject
+        {
+            public double r1;
+            public double rinf;
+            public matinvreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                matinvreport _result = new matinvreport();
+                _result.r1 = r1;
+                _result.rinf = rinf;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        Inversion of a matrix given by its LU decomposition.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            double[] work = new double[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            apserv.sinteger sinfo = new apserv.sinteger();
+
+            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, _params), "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, _params);
+            rep.rinf = rcond.rmatrixlurcondinf(a, n, _params);
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold(_params)) || (double)(rep.rinf)<(double)(rcond.rcondthreshold(_params)) )
+            {
+                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];
+            sinfo.val = 1;
+            rmatrixluinverserec(a, 0, n, work, sinfo, rep, _params);
+            info = sinfo.val;
+            
+            //
+            // 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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "RMatrixInverse: A contains infinite or NaN values!");
+            trfac.rmatrixlu(ref a, n, n, ref pivots, _params);
+            rmatrixluinverse(ref a, pivots, n, ref info, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Inversion of a matrix given by its LU decomposition.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            complex[] work = new complex[0];
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            complex v = 0;
+            apserv.sinteger sinfo = new apserv.sinteger();
+
+            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, _params), "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, _params);
+            rep.rinf = rcond.cmatrixlurcondinf(a, n, _params);
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold(_params)) || (double)(rep.rinf)<(double)(rcond.rcondthreshold(_params)) )
+            {
+                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];
+            sinfo.val = 1;
+            cmatrixluinverserec(a, 0, n, work, sinfo, rep, _params);
+            info = sinfo.val;
+            
+            //
+            // 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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "CMatrixInverse: A contains infinite or NaN values!");
+            trfac.cmatrixlu(ref a, n, n, ref pivots, _params);
+            cmatrixluinverse(ref a, pivots, n, ref info, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Inversion of a symmetric positive definite matrix which is given
+        by Cholesky decomposition.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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 isn't  used/changed  by
+                          function
+                        * if False,  symmetric matrix  A  is  given  by  its lower
+                          triangle, and the  upper triangle isn't 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            rep.rinf = rep.r1;
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold(_params)) || (double)(rep.rinf)<(double)(rcond.rcondthreshold(_params)) )
+            {
+                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(a, 0, n, isupper, tmp, _params);
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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 isn't  used/changed  by
+                          function
+                        * if False,  symmetric matrix  A  is  given  by  its lower
+                          triangle, and the  upper triangle isn't 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,
+            alglib.xparams _params)
+        {
+            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, _params), "SPDMatrixInverse: A contains infinite or NaN values!");
+            info = 1;
+            if( trfac.spdmatrixcholesky(ref a, n, isupper, _params) )
+            {
+                spdmatrixcholeskyinverse(ref a, n, isupper, ref info, rep, _params);
+            }
+            else
+            {
+                info = -3;
+            }
+        }
+
+
+        /*************************************************************************
+        Inversion of a Hermitian positive definite matrix which is given
+        by Cholesky decomposition.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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 isn't  used/changed  by
+                          function
+                        * if False,  symmetric matrix  A  is  given  by  its lower
+                          triangle, and the  upper triangle isn't 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            rep.rinf = rep.r1;
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold(_params)) || (double)(rep.rinf)<(double)(rcond.rcondthreshold(_params)) )
+            {
+                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, _params);
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+          
+        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 isn't  used/changed  by
+                          function
+                        * if False,  symmetric matrix  A  is  given  by  its lower
+                          triangle, and the  upper triangle isn't 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,
+            alglib.xparams _params)
+        {
+            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, _params), "HPDMatrixInverse: A contains infinite or NaN values!");
+            info = 1;
+            if( trfac.hpdmatrixcholesky(ref a, n, isupper, _params) )
+            {
+                hpdmatrixcholeskyinverse(ref a, n, isupper, ref info, rep, _params);
+            }
+            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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+          
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double[] tmp = new double[0];
+            apserv.sinteger sinfo = new apserv.sinteger();
+
+            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, _params), "RMatrixTRInverse: A contains infinite or NaN values!");
+            
+            //
+            // calculate condition numbers
+            //
+            rep.r1 = rcond.rmatrixtrrcond1(a, n, isupper, isunit, _params);
+            rep.rinf = rcond.rmatrixtrrcondinf(a, n, isupper, isunit, _params);
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold(_params)) || (double)(rep.rinf)<(double)(rcond.rcondthreshold(_params)) )
+            {
+                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];
+            sinfo.val = 1;
+            rmatrixtrinverserec(a, 0, n, isupper, isunit, tmp, sinfo, _params);
+            info = sinfo.val;
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            complex[] tmp = new complex[0];
+            apserv.sinteger sinfo = new apserv.sinteger();
+
+            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, _params), "CMatrixTRInverse: A contains infinite or NaN values!");
+            
+            //
+            // calculate condition numbers
+            //
+            rep.r1 = rcond.cmatrixtrrcond1(a, n, isupper, isunit, _params);
+            rep.rinf = rcond.cmatrixtrrcondinf(a, n, isupper, isunit, _params);
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold(_params)) || (double)(rep.rinf)<(double)(rcond.rcondthreshold(_params)) )
+            {
+                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];
+            sinfo.val = 1;
+            cmatrixtrinverserec(a, 0, n, isupper, isunit, tmp, sinfo, _params);
+            info = sinfo.val;
+        }
+
+
+        /*************************************************************************
+        Recursive subroutine for SPD inversion.
+
+        NOTE: this function expects that matris is strictly positive-definite.
+
+          -- ALGLIB routine --
+             10.02.2010
+             Bochkanov Sergey
+        *************************************************************************/
+        public static void spdmatrixcholeskyinverserec(double[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            double[] tmp,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int n1 = 0;
+            int n2 = 0;
+            apserv.sinteger sinfo2 = new apserv.sinteger();
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( n<1 )
+            {
+                return;
+            }
+            tsa = apserv.matrixtilesizea(_params);
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( n<=tsb )
+            {
+                tscur = tsa;
+            }
+            
+            //
+            // Base case
+            //
+            if( n<=tsa )
+            {
+                sinfo2.val = 1;
+                rmatrixtrinverserec(a, offs, n, isupper, false, tmp, sinfo2, _params);
+                alglib.ap.assert(sinfo2.val>0, "SPDMatrixCholeskyInverseRec: integrity check failed");
+                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
+            //
+            apserv.tiledsplit(n, tscur, ref n1, ref n2, _params);
+            
+            //
+            // 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, a, offs, offs+n1, _params);
+                ablas.rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, false, 0, a, offs, offs+n1, _params);
+            }
+            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, a, offs+n1, offs, _params);
+                ablas.rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, false, 0, a, offs+n1, offs, _params);
+            }
+            
+            //
+            // invert first diagonal block
+            //
+            spdmatrixcholeskyinverserec(a, offs, n1, isupper, tmp, _params);
+            
+            //
+            // 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, a, offs, offs, isupper, _params);
+                ablas.rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, false, 1, a, offs, offs+n1, _params);
+            }
+            else
+            {
+                ablas.rmatrixsyrk(n1, n2, 1.0, a, offs+n1, offs, 1, 1.0, a, offs, offs, isupper, _params);
+                ablas.rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, false, 1, a, offs+n1, offs, _params);
+            }
+            
+            //
+            // invert second diagonal block
+            //
+            spdmatrixcholeskyinverserec(a, offs+n1, n2, isupper, tmp, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_spdmatrixcholeskyinverserec(double[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            double[] tmp, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        Triangular matrix inversion, recursive subroutine
+
+        NOTE: this function sets Info on failure, leaves it unchanged on success.
+
+        NOTE: only Tmp[Offs:Offs+N-1] is modified, other entries of the temporary array are not modified
+
+          -- 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(double[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            bool isunit,
+            double[] tmp,
+            apserv.sinteger info,
+            alglib.xparams _params)
+        {
+            int n1 = 0;
+            int n2 = 0;
+            int mn = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double ajj = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+            int i_ = 0;
+
+            if( n<1 )
+            {
+                info.val = -1;
+                return;
+            }
+            tsa = apserv.matrixtilesizea(_params);
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( n<=tsb )
+            {
+                tscur = tsa;
+            }
+            
+            //
+            // Try to activate parallelism
+            //
+            if( n>=2*tsb && (double)(apserv.rmul3(n, n, n, _params)*((double)1/(double)3))>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_rmatrixtrinverserec(a,offs,n,isupper,isunit,tmp,info, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Base case
+            //
+            if( n<=tsa )
+            {
+                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.val = -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 )
+                        {
+                            for(i_=offs+0; i_<=offs+j-1;i_++)
+                            {
+                                tmp[i_] = a[i_,offs+j];
+                            }
+                            for(i=0; i<=j-1; i++)
+                            {
+                                if( i<j-1 )
+                                {
+                                    v = 0.0;
+                                    for(i_=offs+i+1; i_<=offs+j-1;i_++)
+                                    {
+                                        v += a[offs+i,i_]*tmp[i_];
+                                    }
+                                }
+                                else
+                                {
+                                    v = 0;
+                                }
+                                if( !isunit )
+                                {
+                                    a[offs+i,offs+j] = v+a[offs+i,offs+i]*tmp[offs+i];
+                                }
+                                else
+                                {
+                                    a[offs+i,offs+j] = v+tmp[offs+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.val = -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.
+                            //
+                            for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                            {
+                                tmp[i_] = a[i_,offs+j];
+                            }
+                            for(i=j+1; i<=n-1; i++)
+                            {
+                                if( i>j+1 )
+                                {
+                                    v = 0.0;
+                                    for(i_=offs+j+1; i_<=offs+i-1;i_++)
+                                    {
+                                        v += a[offs+i,i_]*tmp[i_];
+                                    }
+                                }
+                                else
+                                {
+                                    v = 0;
+                                }
+                                if( !isunit )
+                                {
+                                    a[offs+i,offs+j] = v+a[offs+i,offs+i]*tmp[offs+i];
+                                }
+                                else
+                                {
+                                    a[offs+i,offs+j] = v+tmp[offs+i];
+                                }
+                            }
+                            for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                            {
+                                a[i_,offs+j] = ajj*a[i_,offs+j];
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Recursive case
+            //
+            apserv.tiledsplit(n, tscur, ref n1, ref n2, _params);
+            mn = apserv.imin2(n1, n2, _params);
+            apserv.touchint(ref mn, _params);
+            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.rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, isunit, 0, a, offs, offs+n1, _params);
+                    rmatrixtrinverserec(a, offs+n1, n2, isupper, isunit, tmp, info, _params);
+                    ablas.rmatrixlefttrsm(n1, n2, a, offs, offs, isupper, isunit, 0, a, offs, offs+n1, _params);
+                }
+                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.rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, isunit, 0, a, offs+n1, offs, _params);
+                    rmatrixtrinverserec(a, offs+n1, n2, isupper, isunit, tmp, info, _params);
+                    ablas.rmatrixrighttrsm(n2, n1, a, offs, offs, isupper, isunit, 0, a, offs+n1, offs, _params);
+                }
+            }
+            rmatrixtrinverserec(a, offs, n1, isupper, isunit, tmp, info, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_rmatrixtrinverserec(double[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            bool isunit,
+            double[] tmp,
+            apserv.sinteger info, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        Triangular matrix inversion, recursive subroutine.
+
+        Info is modified on failure, left unchanged on success.
+
+          -- 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(complex[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            bool isunit,
+            complex[] tmp,
+            apserv.sinteger info,
+            alglib.xparams _params)
+        {
+            int n1 = 0;
+            int n2 = 0;
+            int i = 0;
+            int j = 0;
+            complex v = 0;
+            complex ajj = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+            int mn = 0;
+            int i_ = 0;
+
+            if( n<1 )
+            {
+                info.val = -1;
+                return;
+            }
+            tsa = apserv.matrixtilesizea(_params)/2;
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( n<=tsb )
+            {
+                tscur = tsa;
+            }
+            
+            //
+            // Try to activate parallelism
+            //
+            if( n>=2*tsb && (double)(apserv.rmul3(n, n, n, _params)*((double)4/(double)3))>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_cmatrixtrinverserec(a,offs,n,isupper,isunit,tmp,info, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Base case
+            //
+            if( n<=tsa )
+            {
+                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.val = -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 )
+                        {
+                            for(i_=offs+0; i_<=offs+j-1;i_++)
+                            {
+                                tmp[i_] = a[i_,offs+j];
+                            }
+                            for(i=0; i<=j-1; i++)
+                            {
+                                if( i<j-1 )
+                                {
+                                    v = 0.0;
+                                    for(i_=offs+i+1; i_<=offs+j-1;i_++)
+                                    {
+                                        v += a[offs+i,i_]*tmp[i_];
+                                    }
+                                }
+                                else
+                                {
+                                    v = 0;
+                                }
+                                if( !isunit )
+                                {
+                                    a[offs+i,offs+j] = v+a[offs+i,offs+i]*tmp[offs+i];
+                                }
+                                else
+                                {
+                                    a[offs+i,offs+j] = v+tmp[offs+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.val = -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.
+                            //
+                            for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                            {
+                                tmp[i_] = a[i_,offs+j];
+                            }
+                            for(i=j+1; i<=n-1; i++)
+                            {
+                                if( i>j+1 )
+                                {
+                                    v = 0.0;
+                                    for(i_=offs+j+1; i_<=offs+i-1;i_++)
+                                    {
+                                        v += a[offs+i,i_]*tmp[i_];
+                                    }
+                                }
+                                else
+                                {
+                                    v = 0;
+                                }
+                                if( !isunit )
+                                {
+                                    a[offs+i,offs+j] = v+a[offs+i,offs+i]*tmp[offs+i];
+                                }
+                                else
+                                {
+                                    a[offs+i,offs+j] = v+tmp[offs+i];
+                                }
+                            }
+                            for(i_=offs+j+1; i_<=offs+n-1;i_++)
+                            {
+                                a[i_,offs+j] = ajj*a[i_,offs+j];
+                            }
+                        }
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Recursive case
+            //
+            apserv.tiledsplit(n, tscur, ref n1, ref n2, _params);
+            mn = apserv.imin2(n1, n2, _params);
+            apserv.touchint(ref mn, _params);
+            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.cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, isunit, 0, a, offs, offs+n1, _params);
+                    cmatrixtrinverserec(a, offs+n1, n2, isupper, isunit, tmp, info, _params);
+                    ablas.cmatrixlefttrsm(n1, n2, a, offs, offs, isupper, isunit, 0, a, offs, offs+n1, _params);
+                }
+                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.cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, isunit, 0, a, offs+n1, offs, _params);
+                    cmatrixtrinverserec(a, offs+n1, n2, isupper, isunit, tmp, info, _params);
+                    ablas.cmatrixrighttrsm(n2, n1, a, offs, offs, isupper, isunit, 0, a, offs+n1, offs, _params);
+                }
+            }
+            cmatrixtrinverserec(a, offs, n1, isupper, isunit, tmp, info, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_cmatrixtrinverserec(complex[,] a,
+            int offs,
+            int n,
+            bool isupper,
+            bool isunit,
+            complex[] tmp,
+            apserv.sinteger info, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        private static void rmatrixluinverserec(double[,] a,
+            int offs,
+            int n,
+            double[] work,
+            apserv.sinteger info,
+            matinvreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+            int mn = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( n<1 )
+            {
+                info.val = -1;
+                return;
+            }
+            tsa = apserv.matrixtilesizea(_params);
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( n<=tsb )
+            {
+                tscur = tsa;
+            }
+            
+            //
+            // Try parallelism
+            //
+            if( n>=2*tsb && (double)((double)8/(double)6*apserv.rmul3(n, n, n, _params))>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_rmatrixluinverserec(a,offs,n,work,info,rep, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Base case
+            //
+            if( n<=tsa )
+            {
+                
+                //
+                // Form inv(U)
+                //
+                rmatrixtrinverserec(a, offs, n, true, false, work, info, _params);
+                if( info.val<=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 )
+            //
+            // In-place calculation can be done as follows:
+            // * X := inv(U1)*U12*inv(U2)
+            // * Y := inv(L2)*L12*inv(L1)
+            // * W := inv(L1*U1)+X*Y
+            // * X := -X*inv(L2)
+            // * Y := -inv(U2)*Y
+            // * Z := inv(L2*U2)
+            //
+            // Reordering w.r.t. interdependencies gives us:
+            //
+            // * X := inv(U1)*U12      \ suitable for parallel execution
+            // * Y := L12*inv(L1)      / 
+            //
+            // * X := X*inv(U2)        \
+            // * Y := inv(L2)*Y        | suitable for parallel execution
+            // * W := inv(L1*U1)       / 
+            //
+            // * W := W+X*Y
+            //
+            // * X := -X*inv(L2)       \ suitable for parallel execution
+            // * Y := -inv(U2)*Y       /
+            //
+            // * Z := inv(L2*U2)
+            //
+            apserv.tiledsplit(n, tscur, ref n1, ref n2, _params);
+            mn = apserv.imin2(n1, n2, _params);
+            apserv.touchint(ref mn, _params);
+            alglib.ap.assert(n2>0, "LUInverseRec: internal error!");
+            
+            //
+            // X := inv(U1)*U12
+            // Y := L12*inv(L1)
+            //
+            ablas.rmatrixlefttrsm(n1, n2, a, offs, offs, true, false, 0, a, offs, offs+n1, _params);
+            ablas.rmatrixrighttrsm(n2, n1, a, offs, offs, false, true, 0, a, offs+n1, offs, _params);
+            
+            //
+            // X := X*inv(U2)
+            // Y := inv(L2)*Y
+            // W := inv(L1*U1)
+            //
+            ablas.rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, true, false, 0, a, offs, offs+n1, _params);
+            ablas.rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, false, true, 0, a, offs+n1, offs, _params);
+            rmatrixluinverserec(a, offs, n1, work, info, rep, _params);
+            if( info.val<=0 )
+            {
+                return;
+            }
+            
+            //
+            // W := W+X*Y
+            //
+            ablas.rmatrixgemm(n1, n1, n2, 1.0, a, offs, offs+n1, 0, a, offs+n1, offs, 0, 1.0, a, offs, offs, _params);
+            
+            //
+            // X := -X*inv(L2)
+            // Y := -inv(U2)*Y
+            //
+            ablas.rmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, false, true, 0, a, offs, offs+n1, _params);
+            ablas.rmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, true, false, 0, a, offs+n1, offs, _params);
+            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_];
+                }
+            }
+            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(a, offs+n1, n2, work, info, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_rmatrixluinverserec(double[,] a,
+            int offs,
+            int n,
+            double[] work,
+            apserv.sinteger info,
+            matinvreport rep, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        private static void cmatrixluinverserec(complex[,] a,
+            int offs,
+            int n,
+            complex[] work,
+            apserv.sinteger ssinfo,
+            matinvreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            complex v = 0;
+            int n1 = 0;
+            int n2 = 0;
+            int mn = 0;
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( n<1 )
+            {
+                ssinfo.val = -1;
+                return;
+            }
+            tsa = apserv.matrixtilesizea(_params)/2;
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( n<=tsb )
+            {
+                tscur = tsa;
+            }
+            
+            //
+            // Try parallelism
+            //
+            if( n>=2*tsb && (double)((double)32/(double)6*apserv.rmul3(n, n, n, _params))>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_cmatrixluinverserec(a,offs,n,work,ssinfo,rep, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Base case
+            //
+            if( n<=tsa )
+            {
+                
+                //
+                // Form inv(U)
+                //
+                cmatrixtrinverserec(a, offs, n, true, false, work, ssinfo, _params);
+                if( ssinfo.val<=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 )
+            //
+            // In-place calculation can be done as follows:
+            // * X := inv(U1)*U12*inv(U2)
+            // * Y := inv(L2)*L12*inv(L1)
+            // * W := inv(L1*U1)+X*Y
+            // * X := -X*inv(L2)
+            // * Y := -inv(U2)*Y
+            // * Z := inv(L2*U2)
+            //
+            // Reordering w.r.t. interdependencies gives us:
+            //
+            // * X := inv(U1)*U12      \ suitable for parallel execution
+            // * Y := L12*inv(L1)      / 
+            //
+            // * X := X*inv(U2)        \
+            // * Y := inv(L2)*Y        | suitable for parallel execution
+            // * W := inv(L1*U1)       / 
+            //
+            // * W := W+X*Y
+            //
+            // * X := -X*inv(L2)       \ suitable for parallel execution
+            // * Y := -inv(U2)*Y       /
+            //
+            // * Z := inv(L2*U2)
+            //
+            apserv.tiledsplit(n, tscur, ref n1, ref n2, _params);
+            mn = apserv.imin2(n1, n2, _params);
+            apserv.touchint(ref mn, _params);
+            alglib.ap.assert(n2>0, "LUInverseRec: internal error!");
+            
+            //
+            // X := inv(U1)*U12
+            // Y := L12*inv(L1)
+            //
+            ablas.cmatrixlefttrsm(n1, n2, a, offs, offs, true, false, 0, a, offs, offs+n1, _params);
+            ablas.cmatrixrighttrsm(n2, n1, a, offs, offs, false, true, 0, a, offs+n1, offs, _params);
+            
+            //
+            // X := X*inv(U2)
+            // Y := inv(L2)*Y
+            // W := inv(L1*U1)
+            //
+            ablas.cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, true, false, 0, a, offs, offs+n1, _params);
+            ablas.cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, false, true, 0, a, offs+n1, offs, _params);
+            cmatrixluinverserec(a, offs, n1, work, ssinfo, rep, _params);
+            if( ssinfo.val<=0 )
+            {
+                return;
+            }
+            
+            //
+            // W := W+X*Y
+            //
+            ablas.cmatrixgemm(n1, n1, n2, 1.0, a, offs, offs+n1, 0, a, offs+n1, offs, 0, 1.0, a, offs, offs, _params);
+            
+            //
+            // X := -X*inv(L2)
+            // Y := -inv(U2)*Y
+            //
+            ablas.cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, false, true, 0, a, offs, offs+n1, _params);
+            ablas.cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, true, false, 0, a, offs+n1, offs, _params);
+            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_];
+                }
+            }
+            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(a, offs+n1, n2, work, ssinfo, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_cmatrixluinverserec(complex[,] a,
+            int offs,
+            int n,
+            complex[] work,
+            apserv.sinteger ssinfo,
+            matinvreport rep, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            complex v = 0;
+            int n1 = 0;
+            int n2 = 0;
+            apserv.sinteger sinfo = new apserv.sinteger();
+            int tsa = 0;
+            int tsb = 0;
+            int tscur = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            if( n<1 )
+            {
+                return;
+            }
+            tsa = apserv.matrixtilesizea(_params)/2;
+            tsb = apserv.matrixtilesizeb(_params);
+            tscur = tsb;
+            if( n<=tsb )
+            {
+                tscur = tsa;
+            }
+            
+            //
+            // Base case
+            //
+            if( n<=tsa )
+            {
+                sinfo.val = 1;
+                cmatrixtrinverserec(a, offs, n, isupper, false, tmp, sinfo, _params);
+                alglib.ap.assert(sinfo.val>0, "HPDMatrixCholeskyInverseRec: integrity check failed");
+                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
+            //
+            apserv.tiledsplit(n, tscur, ref n1, ref n2, _params);
+            
+            //
+            // 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, a, offs, offs+n1, _params);
+                ablas.cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, false, 0, a, offs, offs+n1, _params);
+            }
+            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, a, offs+n1, offs, _params);
+                ablas.cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, false, 0, a, offs+n1, offs, _params);
+            }
+            
+            //
+            // invert first diagonal block
+            //
+            hpdmatrixcholeskyinverserec(ref a, offs, n1, isupper, ref tmp, _params);
+            
+            //
+            // update first diagonal block with off-diagonal block,
+            // update off-diagonal block
+            //
+            if( isupper )
+            {
+                ablas.cmatrixherk(n1, n2, 1.0, a, offs, offs+n1, 0, 1.0, a, offs, offs, isupper, _params);
+                ablas.cmatrixrighttrsm(n1, n2, a, offs+n1, offs+n1, isupper, false, 2, a, offs, offs+n1, _params);
+            }
+            else
+            {
+                ablas.cmatrixherk(n1, n2, 1.0, a, offs+n1, offs, 2, 1.0, a, offs, offs, isupper, _params);
+                ablas.cmatrixlefttrsm(n2, n1, a, offs+n1, offs+n1, isupper, false, 2, a, offs+n1, offs, _params);
+            }
+            
+            //
+            // invert second diagonal block
+            //
+            hpdmatrixcholeskyinverserec(ref a, offs+n1, n2, isupper, ref tmp, _params);
+        }
+
+
+    }
+    public class ortfac
+    {
+        /*************************************************************************
+        QR decomposition of a rectangular matrix of size MxN
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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 ts = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            tau = new double[0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            minmn = Math.Min(m, n);
+            ts = apserv.matrixtilesizeb(_params);
+            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, ts];
+            tmpt = new double[ts, 2*ts];
+            tmpr = new double[2*ts, n];
+            
+            //
+            // Blocked code
+            //
+            blockstart = 0;
+            while( blockstart!=minmn )
+            {
+                
+                //
+                // Determine block size
+                //
+                blocksize = minmn-blockstart;
+                if( blocksize>ts )
+                {
+                    blocksize = ts;
+                }
+                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, tmpa, 0, 0, _params);
+                rmatrixqrbasecase(ref tmpa, rowscount, blocksize, ref work, ref t, ref taubuf, _params);
+                ablas.rmatrixcopy(rowscount, blocksize, tmpa, 0, 0, a, blockstart, blockstart, _params);
+                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*ts || rowscount>=4*ts )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        rmatrixblockreflector(ref tmpa, ref taubuf, true, rowscount, blocksize, ref tmpt, ref work, _params);
+                        
+                        //
+                        // 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, tmpr, 0, 0, _params);
+                        ablas.rmatrixgemm(blocksize, n-blockstart-blocksize, blocksize, 1.0, tmpt, 0, 0, 1, tmpr, 0, 0, 0, 0.0, tmpr, blocksize, 0, _params);
+                        ablas.rmatrixgemm(rowscount, n-blockstart-blocksize, blocksize, 1.0, tmpa, 0, 0, 0, tmpr, blocksize, 0, 0, 1.0, a, blockstart, blockstart+blocksize, _params);
+                    }
+                    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;
+                            ablas.applyreflectionfromtheleft(ref a, taubuf[i], t, blockstart+i, m-1, blockstart+blocksize, n-1, ref work, _params);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart+blocksize;
+            }
+        }
+
+
+        /*************************************************************************
+        LQ decomposition of a rectangular matrix of size MxN
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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 ts = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            tau = new double[0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            minmn = Math.Min(m, n);
+            ts = apserv.matrixtilesizeb(_params);
+            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[ts, n];
+            tmpt = new double[ts, 2*ts];
+            tmpr = new double[m, 2*ts];
+            
+            //
+            // Blocked code
+            //
+            blockstart = 0;
+            while( blockstart!=minmn )
+            {
+                
+                //
+                // Determine block size
+                //
+                blocksize = minmn-blockstart;
+                if( blocksize>ts )
+                {
+                    blocksize = ts;
+                }
+                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, tmpa, 0, 0, _params);
+                rmatrixlqbasecase(ref tmpa, blocksize, columnscount, ref work, ref t, ref taubuf, _params);
+                ablas.rmatrixcopy(blocksize, columnscount, tmpa, 0, 0, a, blockstart, blockstart, _params);
+                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*ts )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        rmatrixblockreflector(ref tmpa, ref taubuf, false, columnscount, blocksize, ref tmpt, ref work, _params);
+                        
+                        //
+                        // 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, tmpr, 0, 0, _params);
+                        ablas.rmatrixgemm(m-blockstart-blocksize, blocksize, blocksize, 1.0, tmpr, 0, 0, 0, tmpt, 0, 0, 0, 0.0, tmpr, 0, blocksize, _params);
+                        ablas.rmatrixgemm(m-blockstart-blocksize, columnscount, blocksize, 1.0, tmpr, 0, blocksize, 0, tmpa, 0, 0, 0, 1.0, a, blockstart+blocksize, blockstart, _params);
+                    }
+                    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;
+                            ablas.applyreflectionfromtheright(ref a, taubuf[i], t, blockstart+blocksize, m-1, blockstart+i, n-1, ref work, _params);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart+blocksize;
+            }
+        }
+
+
+        /*************************************************************************
+        QR decomposition of a rectangular complex matrix of size MxN
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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 ts = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            tau = new complex[0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            ts = apserv.matrixtilesizeb(_params)/2;
+            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, ts];
+            tmpt = new complex[ts, ts];
+            tmpr = new complex[2*ts, n];
+            
+            //
+            // Blocked code
+            //
+            blockstart = 0;
+            while( blockstart!=minmn )
+            {
+                
+                //
+                // Determine block size
+                //
+                blocksize = minmn-blockstart;
+                if( blocksize>ts )
+                {
+                    blocksize = ts;
+                }
+                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, _params);
+                cmatrixqrbasecase(ref tmpa, rowscount, blocksize, ref work, ref t, ref taubuf, _params);
+                ablas.cmatrixcopy(rowscount, blocksize, tmpa, 0, 0, ref a, blockstart, blockstart, _params);
+                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*ts )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        cmatrixblockreflector(ref tmpa, ref taubuf, true, rowscount, blocksize, ref tmpt, ref work, _params);
+                        
+                        //
+                        // 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, tmpr, 0, 0, _params);
+                        ablas.cmatrixgemm(blocksize, n-blockstart-blocksize, blocksize, 1.0, tmpt, 0, 0, 2, tmpr, 0, 0, 0, 0.0, tmpr, blocksize, 0, _params);
+                        ablas.cmatrixgemm(rowscount, n-blockstart-blocksize, blocksize, 1.0, tmpa, 0, 0, 0, tmpr, blocksize, 0, 0, 1.0, a, blockstart, blockstart+blocksize, _params);
+                    }
+                    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, _params);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart+blocksize;
+            }
+        }
+
+
+        /*************************************************************************
+        LQ decomposition of a rectangular complex matrix of size MxN
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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 ts = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            tau = new complex[0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            ts = apserv.matrixtilesizeb(_params)/2;
+            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[ts, n];
+            tmpt = new complex[ts, ts];
+            tmpr = new complex[m, 2*ts];
+            
+            //
+            // Blocked code
+            //
+            blockstart = 0;
+            while( blockstart!=minmn )
+            {
+                
+                //
+                // Determine block size
+                //
+                blocksize = minmn-blockstart;
+                if( blocksize>ts )
+                {
+                    blocksize = ts;
+                }
+                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, _params);
+                cmatrixlqbasecase(ref tmpa, blocksize, columnscount, ref work, ref t, ref taubuf, _params);
+                ablas.cmatrixcopy(blocksize, columnscount, tmpa, 0, 0, ref a, blockstart, blockstart, _params);
+                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*ts )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        cmatrixblockreflector(ref tmpa, ref taubuf, false, columnscount, blocksize, ref tmpt, ref work, _params);
+                        
+                        //
+                        // 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, tmpr, 0, 0, _params);
+                        ablas.cmatrixgemm(m-blockstart-blocksize, blocksize, blocksize, 1.0, tmpr, 0, 0, 0, tmpt, 0, 0, 0, 0.0, tmpr, 0, blocksize, _params);
+                        ablas.cmatrixgemm(m-blockstart-blocksize, columnscount, blocksize, 1.0, tmpr, 0, blocksize, 0, tmpa, 0, 0, 0, 1.0, a, blockstart+blocksize, blockstart, _params);
+                    }
+                    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, _params);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart+blocksize;
+            }
+        }
+
+
+        /*************************************************************************
+        Partial unpacking of matrix Q from the QR decomposition of a matrix A
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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 ts = 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
+            //
+            ts = apserv.matrixtilesizeb(_params);
+            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, ts];
+            tmpt = new double[ts, 2*ts];
+            tmpr = new double[2*ts, qcolumns];
+            
+            //
+            // Blocked code
+            //
+            blockstart = ts*(refcnt/ts);
+            blocksize = refcnt-blockstart;
+            while( blockstart>=0 )
+            {
+                rowscount = m-blockstart;
+                if( blocksize>0 )
+                {
+                    
+                    //
+                    // Copy current block
+                    //
+                    ablas.rmatrixcopy(rowscount, blocksize, a, blockstart, blockstart, tmpa, 0, 0, _params);
+                    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*ts )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        rmatrixblockreflector(ref tmpa, ref taubuf, true, rowscount, blocksize, ref tmpt, ref work, _params);
+                        
+                        //
+                        // 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, tmpr, 0, 0, _params);
+                        ablas.rmatrixgemm(blocksize, qcolumns, blocksize, 1.0, tmpt, 0, 0, 0, tmpr, 0, 0, 0, 0.0, tmpr, blocksize, 0, _params);
+                        ablas.rmatrixgemm(rowscount, qcolumns, blocksize, 1.0, tmpa, 0, 0, 0, tmpr, blocksize, 0, 0, 1.0, q, blockstart, 0, _params);
+                    }
+                    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;
+                            ablas.applyreflectionfromtheleft(ref q, taubuf[i], t, blockstart+i, m-1, 0, qcolumns-1, ref work, _params);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart-ts;
+                blocksize = ts;
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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 ts = 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
+            //
+            ts = apserv.matrixtilesizeb(_params);
+            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[ts, n];
+            tmpt = new double[ts, 2*ts];
+            tmpr = new double[qrows, 2*ts];
+            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 = ts*(refcnt/ts);
+            blocksize = refcnt-blockstart;
+            while( blockstart>=0 )
+            {
+                columnscount = n-blockstart;
+                if( blocksize>0 )
+                {
+                    
+                    //
+                    // Copy submatrix
+                    //
+                    ablas.rmatrixcopy(blocksize, columnscount, a, blockstart, blockstart, tmpa, 0, 0, _params);
+                    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*ts )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        rmatrixblockreflector(ref tmpa, ref taubuf, false, columnscount, blocksize, ref tmpt, ref work, _params);
+                        
+                        //
+                        // 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, tmpr, 0, 0, _params);
+                        ablas.rmatrixgemm(qrows, blocksize, blocksize, 1.0, tmpr, 0, 0, 0, tmpt, 0, 0, 1, 0.0, tmpr, 0, blocksize, _params);
+                        ablas.rmatrixgemm(qrows, columnscount, blocksize, 1.0, tmpr, 0, blocksize, 0, tmpa, 0, 0, 0, 1.0, q, 0, blockstart, _params);
+                    }
+                    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;
+                            ablas.applyreflectionfromtheright(ref q, taubuf[i], t, 0, qrows-1, blockstart+i, n-1, ref work, _params);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart-ts;
+                blocksize = ts;
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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 ts = 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
+            //
+            ts = apserv.matrixtilesizeb(_params)/2;
+            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, ts];
+            tmpt = new complex[ts, ts];
+            tmpr = new complex[2*ts, 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 = ts*(refcnt/ts);
+            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, _params);
+                    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*ts )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        cmatrixblockreflector(ref tmpa, ref taubuf, true, rowscount, blocksize, ref tmpt, ref work, _params);
+                        
+                        //
+                        // 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, tmpr, 0, 0, _params);
+                        ablas.cmatrixgemm(blocksize, qcolumns, blocksize, 1.0, tmpt, 0, 0, 0, tmpr, 0, 0, 0, 0.0, tmpr, blocksize, 0, _params);
+                        ablas.cmatrixgemm(rowscount, qcolumns, blocksize, 1.0, tmpa, 0, 0, 0, tmpr, blocksize, 0, 0, 1.0, q, blockstart, 0, _params);
+                    }
+                    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, _params);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart-ts;
+                blocksize = ts;
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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 ts = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            q = new complex[0,0];
+
+            if( m<=0 || n<=0 )
+            {
+                return;
+            }
+            
+            //
+            // Init
+            //
+            ts = apserv.matrixtilesizeb(_params)/2;
+            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[ts, n];
+            tmpt = new complex[ts, ts];
+            tmpr = new complex[qrows, 2*ts];
+            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 = ts*(refcnt/ts);
+            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, _params);
+                    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*ts )
+                    {
+                        
+                        //
+                        // Prepare block reflector
+                        //
+                        cmatrixblockreflector(ref tmpa, ref taubuf, false, columnscount, blocksize, ref tmpt, ref work, _params);
+                        
+                        //
+                        // 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, tmpr, 0, 0, _params);
+                        ablas.cmatrixgemm(qrows, blocksize, blocksize, 1.0, tmpr, 0, 0, 0, tmpt, 0, 0, 2, 0.0, tmpr, 0, blocksize, _params);
+                        ablas.cmatrixgemm(qrows, columnscount, blocksize, 1.0, tmpr, 0, blocksize, 0, tmpa, 0, 0, 0, 1.0, q, 0, blockstart, _params);
+                    }
+                    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, _params);
+                        }
+                    }
+                }
+                
+                //
+                // Advance
+                //
+                blockstart = blockstart-ts;
+                blocksize = ts;
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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];
+                }
+                ablas.generatereflection(ref t, m-i, ref tmp, _params);
+                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
+                    //
+                    ablas.applyreflectionfromtheleft(ref a, tau[i], t, i, m-1, i+1, n-1, ref work, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+            double tmp = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            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_];
+                }
+                ablas.generatereflection(ref t, n-i, ref tmp, _params);
+                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
+                    //
+                    ablas.applyreflectionfromtheright(ref a, tau[i], t, i+1, m-1, i, n-1, ref work, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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^T).
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            double[] work = new double[0];
+            double[] t = new double[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;
+            }
+            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];
+                for(i=0; i<=n-1; i++)
+                {
+                    tauq[i] = 0.0;
+                    taup[i] = 0.0;
+                }
+            }
+            else
+            {
+                tauq = new double[m];
+                taup = new double[m];
+                for(i=0; i<=m-1; i++)
+                {
+                    tauq[i] = 0.0;
+                    taup[i] = 0.0;
+                }
+            }
+            
+            //
+            // Try to use MKL code
+            //
+            // NOTE: buffers Work[] and T[] are used for temporary storage of diagonals;
+            // because they are present in A[], we do not use them.
+            //
+            if( ablasmkl.rmatrixbdmkl(a, m, n, work, t, tauq, taup, _params) )
+            {
+                return;
+            }
+            
+            //
+            // ALGLIB code
+            //
+            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];
+                    }
+                    ablas.generatereflection(ref t, m-i, ref ltau, _params);
+                    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
+                    //
+                    ablas.applyreflectionfromtheleft(ref a, ltau, t, i, m-1, i+1, n-1, ref work, _params);
+                    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_];
+                        }
+                        ablas.generatereflection(ref t, n-1-i, ref ltau, _params);
+                        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
+                        //
+                        ablas.applyreflectionfromtheright(ref a, ltau, t, i+1, m-1, i+1, n-1, ref work, _params);
+                    }
+                    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_];
+                    }
+                    ablas.generatereflection(ref t, n-i, ref ltau, _params);
+                    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
+                    //
+                    ablas.applyreflectionfromtheright(ref a, ltau, t, i+1, m-1, i, n-1, ref work, _params);
+                    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];
+                        }
+                        ablas.generatereflection(ref t, m-1-i, ref ltau, _params);
+                        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
+                        //
+                        ablas.applyreflectionfromtheleft(ref a, ltau, t, i+1, m-1, i+1, n-1, ref work, _params);
+                    }
+                    else
+                    {
+                        tauq[i] = 0;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking matrix Q which reduces a matrix to bidiagonal form.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+          
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        Multiplication by matrix Q which reduces matrix A to  bidiagonal form.
+
+        The algorithm allows pre- or post-multiply by Q or Q'.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+          
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int istep = 0;
+            double[] v = new double[0];
+            double[] work = new double[0];
+            double[] dummy = 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!");
+            
+            //
+            // Try to use MKL code
+            //
+            if( ablasmkl.rmatrixbdmultiplybymkl(qp, m, n, tauq, dummy, z, zrows, zcolumns, true, fromtheright, dotranspose, _params) )
+            {
+                return;
+            }
+            
+            //
+            // 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 )
+                    {
+                        ablas.applyreflectionfromtheright(ref z, tauq[i], v, 0, zrows-1, i, m-1, ref work, _params);
+                    }
+                    else
+                    {
+                        ablas.applyreflectionfromtheleft(ref z, tauq[i], v, i, m-1, 0, zcolumns-1, ref work, _params);
+                    }
+                    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 )
+                        {
+                            ablas.applyreflectionfromtheright(ref z, tauq[i], v, 0, zrows-1, i+1, m-1, ref work, _params);
+                        }
+                        else
+                        {
+                            ablas.applyreflectionfromtheleft(ref z, tauq[i], v, i+1, m-1, 0, zcolumns-1, ref work, _params);
+                        }
+                        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double[] v = new double[0];
+            double[] work = new double[0];
+            double[] dummy = 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 )
+                        {
+                            ablas.applyreflectionfromtheright(ref z, taup[i], v, 0, zrows-1, i+1, n-1, ref work, _params);
+                        }
+                        else
+                        {
+                            ablas.applyreflectionfromtheleft(ref z, taup[i], v, i+1, n-1, 0, zcolumns-1, ref work, _params);
+                        }
+                        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 )
+                    {
+                        ablas.applyreflectionfromtheright(ref z, taup[i], v, 0, zrows-1, i, n-1, ref work, _params);
+                    }
+                    else
+                    {
+                        ablas.applyreflectionfromtheleft(ref z, taup[i], v, i, n-1, 0, zcolumns-1, ref work, _params);
+                    }
+                    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,
+            alglib.xparams _params)
+        {
+            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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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;
+            }
+            
+            //
+            // Allocate place
+            //
+            tau = new double[n-2+1];
+            t = new double[n+1];
+            work = new double[n-1+1];
+            
+            //
+            // MKL version
+            //
+            if( ablasmkl.rmatrixhessenbergmkl(a, n, tau, _params) )
+            {
+                return;
+            }
+            
+            //
+            // ALGLIB version
+            //
+            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];
+                }
+                ablas.generatereflection(ref t, n-i-1, ref v, _params);
+                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
+                //
+                ablas.applyreflectionfromtheright(ref a, v, t, 0, n-1, i+1, n-1, ref work, _params);
+                
+                //
+                // Apply H(i) to A(i+1:ihi,i+1:n) from the left
+                //
+                ablas.applyreflectionfromtheleft(ref a, v, t, i+1, n-1, i+1, n-1, ref work, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Unpacking matrix Q which reduces matrix A to upper Hessenberg form
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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;
+                    }
+                }
+            }
+            
+            //
+            // MKL version
+            //
+            if( ablasmkl.rmatrixhessenbergunpackqmkl(a, n, tau, q, _params) )
+            {
+                return;
+            }
+            
+            //
+            // ALGLIB version: 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;
+                ablas.applyreflectionfromtheright(ref q, tau[i], v, 0, n-1, i+1, n-1, ref work, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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];
+            }
+            
+            //
+            // Try to use MKL
+            //
+            if( ablasmkl.smatrixtdmkl(a, n, isupper, tau, d, e, _params) )
+            {
+                return;
+            }
+            
+            //
+            // ALGLIB version
+            //
+            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];
+                    ablas.generatereflection(ref t, i+1, ref taui, _params);
+                    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, _params);
+                        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, _params);
+                        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];
+                    }
+                    ablas.generatereflection(ref t, n-i-1, ref taui, _params);
+                    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, _params);
+                        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, _params);
+                        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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;
+                    }
+                }
+            }
+            
+            //
+            // MKL version
+            //
+            if( ablasmkl.smatrixtdunpackqmkl(a, n, isupper, tau, q, _params) )
+            {
+                return;
+            }
+            
+            //
+            // ALGLIB version: 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;
+                    ablas.applyreflectionfromtheleft(ref q, tau[i], v, 0, i, 0, n-1, ref work, _params);
+                }
+            }
+            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;
+                    ablas.applyreflectionfromtheleft(ref q, tau[i], v, i+1, n-1, 0, n-1, ref work, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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];
+
+            
+            //
+            // Init and test
+            //
+            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];
+            
+            //
+            // MKL version
+            //
+            if( ablasmkl.hmatrixtdmkl(a, n, isupper, tau, d, e, _params) )
+            {
+                return;
+            }
+            
+            //
+            // ALGLIB version
+            //
+            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, _params);
+                    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, _params);
+                        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, _params);
+                    }
+                    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, _params);
+                    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, _params);
+                        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, _params);
+                    }
+                    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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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];
+            
+            //
+            // MKL version
+            //
+            if( ablasmkl.hmatrixtdunpackqmkl(a, n, isupper, tau, q, _params) )
+            {
+                return;
+            }
+            
+            //
+            // ALGLIB version
+            //
+            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;
+                    }
+                }
+            }
+            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, _params);
+                }
+            }
+            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, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 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 : apobject
+        {
+            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()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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];
+            }
+            public override alglib.apobject make_copy()
+            {
+                fblslincgstate _result = new fblslincgstate();
+                _result.e1 = e1;
+                _result.e2 = e2;
+                _result.x = (double[])x.Clone();
+                _result.ax = (double[])ax.Clone();
+                _result.xax = xax;
+                _result.n = n;
+                _result.rk = (double[])rk.Clone();
+                _result.rk1 = (double[])rk1.Clone();
+                _result.xk = (double[])xk.Clone();
+                _result.xk1 = (double[])xk1.Clone();
+                _result.pk = (double[])pk.Clone();
+                _result.pk1 = (double[])pk1.Clone();
+                _result.b = (double[])b.Clone();
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.tmp2 = (double[])tmp2.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        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,
+            double[] xb,
+            ref double[] tmp,
+            alglib.xparams _params)
+        {
+            double v = 0;
+            int i_ = 0;
+
+            if( n<=0 )
+            {
+                return;
+            }
+            if( alglib.ap.len(tmp)<n )
+            {
+                tmp = new double[n];
+            }
+            
+            //
+            // Scale right part
+            //
+            v = 1/math.sqr(sqrtscalea);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                xb[i_] = v*xb[i_];
+            }
+            
+            //
+            // Solve A = L*L' or A=U'*U
+            //
+            if( isupper )
+            {
+                
+                //
+                // Solve U'*y=b first.
+                //
+                ablas.rmatrixtrsv(n, cha, 0, 0, true, false, 1, xb, 0, _params);
+                
+                //
+                // Solve U*x=y then.
+                //
+                ablas.rmatrixtrsv(n, cha, 0, 0, true, false, 0, xb, 0, _params);
+            }
+            else
+            {
+                
+                //
+                // Solve L*y=b first
+                //
+                ablas.rmatrixtrsv(n, cha, 0, 0, false, false, 0, xb, 0, _params);
+                
+                //
+                // Solve L'*x=y then.
+                //
+                ablas.rmatrixtrsv(n, cha, 0, 0, false, false, 1, xb, 0, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, buf, offstmp1, _params);
+            ablas.rmatrixmv(n, m, a, 0, 0, 1, buf, offstmp1, buf, offstmp2, _params);
+            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, buf, offstmp1, _params);
+                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, buf, offstmp2, _params);
+                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, buf, offstmp1, _params);
+            ablas.rmatrixmv(n, m, a, 0, 0, 1, buf, offstmp1, buf, offstmp2, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 = 359;
+                k = -58;
+                rk2 = -919;
+                rk12 = -909;
+                pap = 81;
+                s = 255;
+                betak = 74;
+                v1 = -788;
+                v2 = 809;
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            apserv.rvectorsetlengthatleast(ref tmp1, Math.Max(m, n)+1, _params);
+            apserv.rvectorsetlengthatleast(ref tmp2, Math.Min(m, n), _params);
+            
+            //
+            // Call basecase QR
+            //
+            ortfac.rmatrixqrbasecase(ref a, m, n, ref tmp0, ref tmp1, ref tmp2, _params);
+            
+            //
+            // 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 bdsvd
+    {
+        /*************************************************************************
+        Singular value decomposition of a bidiagonal matrix (extended algorithm)
+
+        COMMERCIAL EDITION OF ALGLIB:
+
+          ! Commercial version of ALGLIB includes one  important  improvement   of
+          ! this function, which can be used from C++ and C#:
+          ! * Intel MKL support (lightweight Intel MKL is shipped with ALGLIB)
+          !
+          ! Intel MKL gives approximately constant  (with  respect  to  number  of
+          ! worker threads) acceleration factor which depends on CPU  being  used,
+          ! problem  size  and  "baseline"  ALGLIB  edition  which  is  used   for
+          ! comparison.
+          !
+          ! Generally, commercial ALGLIB is several times faster than  open-source
+          ! generic C edition, and many times faster than open-source C# edition.
+          !
+          ! Multithreaded acceleration is NOT supported for this function.
+          !
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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).
+            
+        NOTE: multiplication U*Q is performed by means of transposition to internal
+              buffer, multiplication and backward transposition. It helps to avoid
+              costly columnwise operations and speed-up algorithm.
+
+        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,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+            double[] en = new double[0];
+            double[] d1 = new double[0];
+            double[] e1 = new double[0];
+            int i_ = 0;
+            int i1_ = 0;
+
+            e = (double[])e.Clone();
+
+            result = false;
+            
+            //
+            // Try to use MKL
+            //
+            en = new double[n];
+            for(i=0; i<=n-2; i++)
+            {
+                en[i] = e[i];
+            }
+            en[n-1] = 0.0;
+            if( ablasmkl.rmatrixbdsvdmkl(d, en, n, isupper, u, nru, c, ncc, vt, ncvt, ref result, _params) )
+            {
+                return result;
+            }
+            
+            //
+            // Use ALGLIB code
+            //
+            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(d1, e1, n, isupper, isfractionalaccuracyrequired, u, 0, nru, c, 0, ncc, vt, 0, ncvt, _params);
+            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,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            e = (double[])e.Clone();
+
+            result = bidiagonalsvddecompositioninternal(d, e, n, isupper, isfractionalaccuracyrequired, u, 1, nru, c, 1, ncc, vt, 1, ncvt, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal working subroutine for bidiagonal decomposition
+        *************************************************************************/
+        private static bool bidiagonalsvddecompositioninternal(double[] d,
+            double[] e,
+            int n,
+            bool isupper,
+            bool isfractionalaccuracyrequired,
+            double[,] uu,
+            int ustart,
+            int nru,
+            double[,] c,
+            int cstart,
+            int ncc,
+            double[,] vt,
+            int vstart,
+            int ncvt,
+            alglib.xparams _params)
+        {
+            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 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];
+            double[,] ut = new double[0,0];
+            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;
+            fwddir = true;
+            if( nru>0 )
+            {
+                ut = new double[ustart+n, ustart+nru];
+                ablas.rmatrixtranspose(nru, n, uu, ustart, ustart, ut, ustart, ustart, _params);
+            }
+            
+            //
+            // 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, _params);
+                    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.applyrotationsfromtheleft(fwddir, 1+ustart-1, n+ustart-1, ustart, uend, work0, work1, ut, utemp, _params);
+                }
+                if( ncc>0 )
+                {
+                    rotations.applyrotationsfromtheleft(fwddir, 1+cstart-1, n+cstart-1, cstart, cend, work0, work1, c, ctemp, _params);
+                }
+            }
+            
+            //
+            // 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, _params);
+                    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*ut[mm1,i_];
+                        }
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            utemp[i_] = utemp[i_] + sinl*ut[mm0,i_];
+                        }
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            ut[mm0,i_] = cosl*ut[mm0,i_];
+                        }
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            ut[mm0,i_] = ut[mm0,i_] - sinl*ut[mm1,i_];
+                        }
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            ut[mm1,i_] = 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;
+                            }
+                            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;
+                            }
+                            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, _params);
+                    }
+                    else
+                    {
+                        sll = Math.Abs(d[m]);
+                        svd2x2(d[ll], e[ll], d[ll+1], ref shift, ref r, _params);
+                    }
+                    
+                    //
+                    // 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, _params);
+                            if( i>ll )
+                            {
+                                e[i-1] = oldsn*r;
+                            }
+                            rotations.generaterotation(oldcs*r, d[i+1]*sn, ref oldcs, ref oldsn, ref tmp, _params);
+                            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, vt, vttemp, _params);
+                        }
+                        if( nru>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(fwddir, ll+ustart-1, m+ustart-1, ustart, uend, work2, work3, ut, utemp, _params);
+                        }
+                        if( ncc>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(fwddir, ll+cstart-1, m+cstart-1, cstart, cend, work2, work3, c, ctemp, _params);
+                        }
+                        
+                        //
+                        // 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, _params);
+                            if( i<m )
+                            {
+                                e[i] = oldsn*r;
+                            }
+                            rotations.generaterotation(oldcs*r, d[i-1]*sn, ref oldcs, ref oldsn, ref tmp, _params);
+                            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, vt, vttemp, _params);
+                        }
+                        if( nru>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(!fwddir, ll+ustart-1, m+ustart-1, ustart, uend, work0, work1, ut, utemp, _params);
+                        }
+                        if( ncc>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(!fwddir, ll+cstart-1, m+cstart-1, cstart, cend, work0, work1, c, ctemp, _params);
+                        }
+                        
+                        //
+                        // 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], _params)+shift/d[ll]);
+                        g = e[ll];
+                        for(i=ll; i<=m-1; i++)
+                        {
+                            rotations.generaterotation(f, g, ref cosr, ref sinr, ref r, _params);
+                            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, _params);
+                            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, vt, vttemp, _params);
+                        }
+                        if( nru>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(fwddir, ll+ustart-1, m+ustart-1, ustart, uend, work2, work3, ut, utemp, _params);
+                        }
+                        if( ncc>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(fwddir, ll+cstart-1, m+cstart-1, cstart, cend, work2, work3, c, ctemp, _params);
+                        }
+                        
+                        //
+                        // 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], _params)+shift/d[m]);
+                        g = e[m-1];
+                        for(i=m; i>=ll+1; i--)
+                        {
+                            rotations.generaterotation(f, g, ref cosr, ref sinr, ref r, _params);
+                            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, _params);
+                            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, vt, vttemp, _params);
+                        }
+                        if( nru>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(!fwddir, ll+ustart-1, m+ustart-1, ustart, uend, work0, work1, ut, utemp, _params);
+                        }
+                        if( ncc>0 )
+                        {
+                            rotations.applyrotationsfromtheleft(!fwddir, ll+cstart-1, m+cstart-1, cstart, cend, work0, work1, c, ctemp, _params);
+                        }
+                    }
+                }
+                
+                //
+                // 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_] = ut[isub+ustart-1,i_];
+                        }
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            ut[isub+ustart-1,i_] = ut[j+ustart-1,i_];
+                        }
+                        for(i_=ustart; i_<=uend;i_++)
+                        {
+                            ut[j+ustart-1,i_] = 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_];
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Copy U back from temporary storage
+            //
+            if( nru>0 )
+            {
+                ablas.rmatrixtranspose(n, nru, ut, ustart, ustart, uu, ustart, ustart, _params);
+            }
+            return result;
+        }
+
+
+        private static double extsignbdsqr(double a,
+            double b,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params)*extsignbdsqr(1, gt, _params);
+                        }
+                        else
+                        {
+                            t = gt/extsignbdsqr(d, ft, _params)+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, _params)*extsignbdsqr(1, csl, _params)*extsignbdsqr(1, f, _params);
+            }
+            if( pmax==2 )
+            {
+                tsign = extsignbdsqr(1, snr, _params)*extsignbdsqr(1, csl, _params)*extsignbdsqr(1, g, _params);
+            }
+            if( pmax==3 )
+            {
+                tsign = extsignbdsqr(1, snr, _params)*extsignbdsqr(1, snl, _params)*extsignbdsqr(1, h, _params);
+            }
+            ssmax = extsignbdsqr(ssmax, tsign, _params);
+            ssmin = extsignbdsqr(ssmin, tsign*extsignbdsqr(1, f, _params)*extsignbdsqr(1, h, _params), _params);
+        }
+
+
+    }
+    public class svd
+    {
+        /*************************************************************************
+        Singular value decomposition of a rectangular matrix.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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 doesn't 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 isn't 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                    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, _params);
+                    ortfac.rmatrixbdunpackpt(a, n, n, taup, nrvt, ref vt, _params);
+                    ortfac.rmatrixbdunpackdiagonals(a, n, n, ref isupper, ref w, ref e, _params);
+                    result = bdsvd.rmatrixbdsvd(ref w, e, n, isupper, false, ref u, 0, ref a, 0, ref vt, ncvt, _params);
+                    return result;
+                }
+                else
+                {
+                    
+                    //
+                    // Left singular vectors (may be full matrix U) to be computed
+                    //
+                    ortfac.rmatrixqr(ref a, m, n, ref tau, _params);
+                    ortfac.rmatrixqrunpackq(a, m, n, tau, ncu, ref u, _params);
+                    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, _params);
+                    ortfac.rmatrixbdunpackpt(a, n, n, taup, nrvt, ref vt, _params);
+                    ortfac.rmatrixbdunpackdiagonals(a, n, n, ref isupper, ref w, ref e, _params);
+                    if( additionalmemory<1 )
+                    {
+                        
+                        //
+                        // No additional memory can be used
+                        //
+                        ortfac.rmatrixbdmultiplybyq(a, n, n, tauq, ref u, m, n, true, false, _params);
+                        result = bdsvd.rmatrixbdsvd(ref w, e, n, isupper, false, ref u, m, ref a, 0, ref vt, ncvt, _params);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Large U. Transforming intermediate matrix T2
+                        //
+                        work = new double[Math.Max(m, n)+1];
+                        ortfac.rmatrixbdunpackq(a, n, n, tauq, n, ref t2, _params);
+                        blas.copymatrix(u, 0, m-1, 0, n-1, ref a, 0, m-1, 0, n-1, _params);
+                        blas.inplacetranspose(ref t2, 0, n-1, 0, n-1, ref work, _params);
+                        result = bdsvd.rmatrixbdsvd(ref w, e, n, isupper, false, ref u, 0, ref t2, n, ref vt, ncvt, _params);
+                        ablas.rmatrixgemm(m, n, n, 1.0, a, 0, 0, 0, t2, 0, 0, 1, 0.0, u, 0, 0, _params);
+                    }
+                    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, _params);
+                    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, _params);
+                    ortfac.rmatrixbdunpackq(a, m, m, tauq, ncu, ref u, _params);
+                    ortfac.rmatrixbdunpackdiagonals(a, m, m, ref isupper, ref w, ref e, _params);
+                    work = new double[m+1];
+                    blas.inplacetranspose(ref u, 0, nru-1, 0, ncu-1, ref work, _params);
+                    result = bdsvd.rmatrixbdsvd(ref w, e, m, isupper, false, ref a, 0, ref u, nru, ref vt, 0, _params);
+                    blas.inplacetranspose(ref u, 0, nru-1, 0, ncu-1, ref work, _params);
+                    return result;
+                }
+                else
+                {
+                    
+                    //
+                    // Right singular vectors (may be full matrix VT) to be computed
+                    //
+                    ortfac.rmatrixlq(ref a, m, n, ref tau, _params);
+                    ortfac.rmatrixlqunpackq(a, m, n, tau, nrvt, ref vt, _params);
+                    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, _params);
+                    ortfac.rmatrixbdunpackq(a, m, m, tauq, ncu, ref u, _params);
+                    ortfac.rmatrixbdunpackdiagonals(a, m, m, ref isupper, ref w, ref e, _params);
+                    work = new double[Math.Max(m, n)+1];
+                    blas.inplacetranspose(ref u, 0, nru-1, 0, ncu-1, ref work, _params);
+                    if( additionalmemory<1 )
+                    {
+                        
+                        //
+                        // No additional memory available
+                        //
+                        ortfac.rmatrixbdmultiplybyp(a, m, m, taup, ref vt, m, n, false, true, _params);
+                        result = bdsvd.rmatrixbdsvd(ref w, e, m, isupper, false, ref a, 0, ref u, nru, ref vt, n, _params);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Large VT. Transforming intermediate matrix T2
+                        //
+                        ortfac.rmatrixbdunpackpt(a, m, m, taup, m, ref t2, _params);
+                        result = bdsvd.rmatrixbdsvd(ref w, e, m, isupper, false, ref a, 0, ref u, nru, ref t2, m, _params);
+                        blas.copymatrix(vt, 0, m-1, 0, n-1, ref a, 0, m-1, 0, n-1, _params);
+                        ablas.rmatrixgemm(m, n, m, 1.0, t2, 0, 0, 0, a, 0, 0, 0, 0.0, vt, 0, 0, _params);
+                    }
+                    blas.inplacetranspose(ref u, 0, nru-1, 0, ncu-1, ref work, _params);
+                    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, _params);
+                ortfac.rmatrixbdunpackq(a, m, n, tauq, ncu, ref u, _params);
+                ortfac.rmatrixbdunpackpt(a, m, n, taup, nrvt, ref vt, _params);
+                ortfac.rmatrixbdunpackdiagonals(a, m, n, ref isupper, ref w, ref e, _params);
+                work = new double[m+1];
+                blas.inplacetranspose(ref u, 0, nru-1, 0, ncu-1, ref work, _params);
+                result = bdsvd.rmatrixbdsvd(ref w, e, minmn, isupper, false, ref a, 0, ref u, nru, ref vt, ncvt, _params);
+                blas.inplacetranspose(ref u, 0, nru-1, 0, ncu-1, ref work, _params);
+                return result;
+            }
+            
+            //
+            // Simple bidiagonal reduction
+            //
+            ortfac.rmatrixbd(ref a, m, n, ref tauq, ref taup, _params);
+            ortfac.rmatrixbdunpackq(a, m, n, tauq, ncu, ref u, _params);
+            ortfac.rmatrixbdunpackpt(a, m, n, taup, nrvt, ref vt, _params);
+            ortfac.rmatrixbdunpackdiagonals(a, m, n, ref isupper, ref w, ref e, _params);
+            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, _params);
+            }
+            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, _params);
+                result = bdsvd.rmatrixbdsvd(ref w, e, minmn, isupper, false, ref u, 0, ref t2, m, ref vt, ncvt, _params);
+                blas.copyandtranspose(t2, 0, minmn-1, 0, m-1, ref u, 0, m-1, 0, minmn-1, _params);
+            }
+            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 : apobject
+        {
+            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()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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();
+            }
+            public override alglib.apobject make_copy()
+            {
+                normestimatorstate _result = new normestimatorstate();
+                _result.n = n;
+                _result.m = m;
+                _result.nstart = nstart;
+                _result.nits = nits;
+                _result.seedval = seedval;
+                _result.x0 = (double[])x0.Clone();
+                _result.x1 = (double[])x1.Clone();
+                _result.t = (double[])t.Clone();
+                _result.xbest = (double[])xbest.Clone();
+                _result.r = (hqrnd.hqrndstate)r.make_copy();
+                _result.x = (double[])x.Clone();
+                _result.mv = (double[])mv.Clone();
+                _result.mtv = (double[])mtv.Clone();
+                _result.needmv = needmv;
+                _result.needmtv = needmtv;
+                _result.repnorm = repnorm;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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(nits>0, "NormEstimatorCreate: NIts<=0");
+            state.m = m;
+            state.n = n;
+            state.nstart = nstart;
+            state.nits = nits;
+            state.seedval = 11;
+            hqrnd.hqrndrandomize(state.r, _params);
+            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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 = 359;
+                m = -58;
+                i = -919;
+                itcnt = -909;
+                v = 81;
+                growth = 255;
+                bestgrowth = 74;
+            }
+            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, _params);
+            }
+            bestgrowth = 0;
+            state.xbest[0] = 1;
+            for(i=1; i<=n-1; i++)
+            {
+                state.xbest[i] = 0;
+            }
+            itcnt = 0;
+        lbl_4:
+            if( itcnt>state.nstart-1 )
+            {
+                goto lbl_6;
+            }
+            do
+            {
+                v = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    state.x0[i] = hqrnd.hqrndnormal(state.r, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            normestimatorrestart(state, _params);
+            while( normestimatoriteration(state, _params) )
+            {
+                if( state.needmv )
+                {
+                    sparse.sparsemv(a, state.x, ref state.mv, _params);
+                    continue;
+                }
+                if( state.needmtv )
+                {
+                    sparse.sparsemtv(a, state.x, ref state.mtv, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            state.rstate.ia = new int[3+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+        }
+
+
+    }
+    public class hsschur
+    {
+        public static void rmatrixinternalschurdecomposition(double[,] h,
+            int n,
+            int tneeded,
+            int zneeded,
+            ref double[] wr,
+            ref double[] wi,
+            ref double[,] z,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double[,] h1 = new double[0,0];
+            double[,] z1 = new double[0,0];
+            double[] wr1 = new double[0];
+            double[] wi1 = new double[0];
+
+            wr = new double[0];
+            wi = new double[0];
+            info = 0;
+
+            
+            //
+            // Allocate space
+            //
+            wr = new double[n];
+            wi = new double[n];
+            if( zneeded==2 )
+            {
+                apserv.rmatrixsetlengthatleast(ref z, n, n, _params);
+            }
+            
+            //
+            // MKL version
+            //
+            if( ablasmkl.rmatrixinternalschurdecompositionmkl(h, n, tneeded, zneeded, wr, wi, z, ref info, _params) )
+            {
+                return;
+            }
+            
+            //
+            // ALGLIB version
+            //
+            h1 = new double[n+1, n+1];
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    h1[1+i,1+j] = h[i,j];
+                }
+            }
+            if( zneeded==1 )
+            {
+                z1 = new double[n+1, n+1];
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        z1[1+i,1+j] = z[i,j];
+                    }
+                }
+            }
+            internalschurdecomposition(ref h1, n, tneeded, zneeded, ref wr1, ref wi1, ref z1, ref info, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                wr[i] = wr1[i+1];
+                wi[i] = wi1[i+1];
+            }
+            if( tneeded!=0 )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        h[i,j] = h1[1+i,1+j];
+                    }
+                }
+            }
+            if( zneeded!=0 )
+            {
+                apserv.rmatrixsetlengthatleast(ref z, n, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        z[i,j] = z1[1+i,1+j];
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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 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 )
+            {
+                apserv.rmatrixsetlengthatleast(ref z, n+1, n+1, _params);
+                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, _params);
+                
+                //
+                // 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;
+            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, _params);
+                        }
+                        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, _params);
+                        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, _params);
+                        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], _params);
+                                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]), _params);
+                                    itemp = blas.vectoridxabsmax(vv, 1, nv+1, _params);
+                                    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], _params);
+                                    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, _params);
+                                    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, _params);
+                            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];
+                            }
+                            apserv.touchint(ref p2, _params);
+                        }
+                        ablas.generatereflection(ref v, nr, ref tau, _params);
+                        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.
+                        //
+                        ablas.applyreflectionfromtheleft(ref h, tau, v, k, k+nr-1, k, i2, ref work, _params);
+                        
+                        //
+                        // Apply G from the right to transform the columns of the
+                        // matrix in rows I1 to min(K+NR,I).
+                        //
+                        ablas.applyreflectionfromtheright(ref h, tau, v, i1, Math.Min(k+nr, i), k, k+nr-1, ref work, _params);
+                        if( wantz )
+                        {
+                            
+                            //
+                            // Accumulate transformations in the matrix Z
+                            //
+                            ablas.applyreflectionfromtheright(ref z, tau, v, 1, n, k, k+nr-1, ref work, _params);
+                        }
+                    }
+                }
+                
+                //
+                // 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, _params);
+                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;
+                
+                //
+                // Block below is never executed; it is necessary just to avoid
+                // "unreachable code" warning about automatically generated code.
+                //
+                // We just need a way to transfer control to the end of the function,
+                // even a fake way which is never actually traversed.
+                //
+                if( apserv.alwaysfalse(_params) )
+                {
+                    alglib.ap.assert(false);
+                    break;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Translation of DLAHQR from LAPACK.
+        *************************************************************************/
+        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,
+            alglib.xparams _params)
+        {
+            double safmin = 0;
+            double tst = 0;
+            double ab = 0;
+            double ba = 0;
+            double aa = 0;
+            double bb = 0;
+            double rt1r = 0;
+            double rt1i = 0;
+            double rt2r = 0;
+            double rt2i = 0;
+            double tr = 0;
+            double det = 0;
+            double rtdisc = 0;
+            double h21s = 0;
+            int i = 0;
+            int i1 = 0;
+            int i2 = 0;
+            int itmax = 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 cs = 0;
+            double h11 = 0;
+            double h12 = 0;
+            double h21 = 0;
+            double h22 = 0;
+            double s = 0;
+            double smlnum = 0;
+            double sn = 0;
+            double sum = 0;
+            double t1 = 0;
+            double t2 = 0;
+            double t3 = 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;
+            
+            //
+            // Quick return if possible
+            //
+            if( n==0 )
+            {
+                return;
+            }
+            if( ilo==ihi )
+            {
+                wr[ilo] = h[ilo,ilo];
+                wi[ilo] = 0;
+                return;
+            }
+            
+            //
+            // ==== clear out the trash ====
+            //
+            for(j=ilo; j<=ihi-3; j++)
+            {
+                h[j+2,j] = 0;
+                h[j+3,j] = 0;
+            }
+            if( ilo<=ihi-2 )
+            {
+                h[ihi,ihi-2] = 0;
+            }
+            nh = ihi-ilo+1;
+            nz = ihiz-iloz+1;
+            
+            //
+            // Set machine-dependent constants for the stopping criterion.
+            //
+            safmin = math.minrealnumber;
+            ulp = math.machineepsilon;
+            smlnum = safmin*(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.
+            //
+            // Setting them to large negative value helps to debug possible errors
+            // due to uninitialized variables; also it helps to avoid compiler
+            // warnings.
+            //
+            i1 = -99999;
+            i2 = -99999;
+            if( wantt )
+            {
+                i1 = 1;
+                i2 = n;
+            }
+            
+            //
+            // ITMAX is the total number of QR iterations allowed.
+            //
+            itmax = 30*Math.Max(10, 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<=itmax; its++)
+                {
+                    
+                    //
+                    // Look for a single small subdiagonal element.
+                    //
+                    for(k=i; k>=l+1; k--)
+                    {
+                        if( (double)(Math.Abs(h[k,k-1]))<=(double)(smlnum) )
+                        {
+                            break;
+                        }
+                        tst = Math.Abs(h[k-1,k-1])+Math.Abs(h[k,k]);
+                        if( (double)(tst)==(double)(0) )
+                        {
+                            if( k-2>=ilo )
+                            {
+                                tst = tst+Math.Abs(h[k-1,k-2]);
+                            }
+                            if( k+1<=ihi )
+                            {
+                                tst = tst+Math.Abs(h[k+1,k]);
+                            }
+                        }
+                        
+                        //
+                        // ==== The following is a conservative small subdiagonal
+                        // .    deflation  criterion due to Ahues & Tisseur (LAWN 122,
+                        // .    1997). It has better mathematical foundation and
+                        // .    improves accuracy in some cases.  ====
+                        //
+                        if( (double)(Math.Abs(h[k,k-1]))<=(double)(ulp*tst) )
+                        {
+                            ab = Math.Max(Math.Abs(h[k,k-1]), Math.Abs(h[k-1,k]));
+                            ba = Math.Min(Math.Abs(h[k,k-1]), Math.Abs(h[k-1,k]));
+                            aa = Math.Max(Math.Abs(h[k,k]), Math.Abs(h[k-1,k-1]-h[k,k]));
+                            bb = Math.Min(Math.Abs(h[k,k]), Math.Abs(h[k-1,k-1]-h[k,k]));
+                            s = aa+ab;
+                            if( (double)(ba*(ab/s))<=(double)(Math.Max(smlnum, ulp*(bb*(aa/s)))) )
+                            {
+                                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( !wantt )
+                    {
+                        i1 = l;
+                        i2 = i;
+                    }
+                    
+                    //
+                    // Shifts
+                    //
+                    if( its==10 )
+                    {
+                        
+                        //
+                        // Exceptional shift.
+                        //
+                        s = Math.Abs(h[l+1,l])+Math.Abs(h[l+2,l+1]);
+                        h11 = dat1*s+h[l,l];
+                        h12 = dat2*s;
+                        h21 = s;
+                        h22 = h11;
+                    }
+                    else
+                    {
+                        if( its==20 )
+                        {
+                            
+                            //
+                            // Exceptional shift.
+                            //
+                            s = Math.Abs(h[i,i-1])+Math.Abs(h[i-1,i-2]);
+                            h11 = dat1*s+h[i,i];
+                            h12 = dat2*s;
+                            h21 = s;
+                            h22 = h11;
+                        }
+                        else
+                        {
+                            
+                            //
+                            // Prepare to use Francis' double shift
+                            // (i.e. 2nd degree generalized Rayleigh quotient)
+                            //
+                            h11 = h[i-1,i-1];
+                            h21 = h[i,i-1];
+                            h12 = h[i-1,i];
+                            h22 = h[i,i];
+                        }
+                    }
+                    s = Math.Abs(h11)+Math.Abs(h12)+Math.Abs(h21)+Math.Abs(h22);
+                    if( (double)(s)==(double)(0) )
+                    {
+                        rt1r = 0;
+                        rt1i = 0;
+                        rt2r = 0;
+                        rt2i = 0;
+                    }
+                    else
+                    {
+                        h11 = h11/s;
+                        h21 = h21/s;
+                        h12 = h12/s;
+                        h22 = h22/s;
+                        tr = (h11+h22)/2;
+                        det = (h11-tr)*(h22-tr)-h12*h21;
+                        rtdisc = Math.Sqrt(Math.Abs(det));
+                        if( (double)(det)>=(double)(0) )
+                        {
+                            
+                            //
+                            // ==== complex conjugate shifts ====
+                            //
+                            rt1r = tr*s;
+                            rt2r = rt1r;
+                            rt1i = rtdisc*s;
+                            rt2i = -rt1i;
+                        }
+                        else
+                        {
+                            
+                            //
+                            // ==== real shifts (use only one of them)  ====
+                            //
+                            rt1r = tr+rtdisc;
+                            rt2r = tr-rtdisc;
+                            if( (double)(Math.Abs(rt1r-h22))<=(double)(Math.Abs(rt2r-h22)) )
+                            {
+                                rt1r = rt1r*s;
+                                rt2r = rt1r;
+                            }
+                            else
+                            {
+                                rt2r = rt2r*s;
+                                rt1r = rt2r;
+                            }
+                            rt1i = 0;
+                            rt2i = 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.  (The following uses scaling to avoid
+                        // overflows and most underflows.)
+                        //
+                        h21s = h[m+1,m];
+                        s = Math.Abs(h[m,m]-rt2r)+Math.Abs(rt2i)+Math.Abs(h21s);
+                        h21s = h[m+1,m]/s;
+                        workv3[1] = h21s*h[m,m+1]+(h[m,m]-rt1r)*((h[m,m]-rt2r)/s)-rt1i*(rt2i/s);
+                        workv3[2] = h21s*(h[m,m]+h[m+1,m+1]-rt1r-rt2r);
+                        workv3[3] = h21s*h[m+2,m+1];
+                        s = Math.Abs(workv3[1])+Math.Abs(workv3[2])+Math.Abs(workv3[3]);
+                        workv3[1] = workv3[1]/s;
+                        workv3[2] = workv3[2]/s;
+                        workv3[3] = workv3[3]/s;
+                        if( m==l )
+                        {
+                            break;
+                        }
+                        if( (double)(Math.Abs(h[m,m-1])*(Math.Abs(workv3[2])+Math.Abs(workv3[3])))<=(double)(ulp*Math.Abs(workv3[1])*(Math.Abs(h[m-1,m-1])+Math.Abs(h[m,m])+Math.Abs(h[m+1,m+1]))) )
+                        {
+                            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];
+                            }
+                        }
+                        ablas.generatereflection(ref workv3, nr, ref t1, _params);
+                        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 )
+                            {
+                                
+                                //
+                                // ==== Use the following instead of
+                                // H( K, K-1 ) = -H( K, K-1 ) to
+                                // avoid a bug when v(2) and v(3)
+                                // underflow. ====
+                                //
+                                h[k,k-1] = h[k,k-1]*(1-t1);
+                            }
+                        }
+                        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;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                
+                //
+                // Failure to converge in remaining number of iterations
+                //
+                if( failflag )
+                {
+                    info = i;
+                    return;
+                }
+                
+                //
+                // Convergence
+                //
+                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, _params);
+                        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, h, work, _params);
+                            }
+                            workc1[1] = cs;
+                            works1[1] = sn;
+                            rotations.applyrotationsfromtheright(true, i1, i-2, i-1, i, workc1, works1, h, work, _params);
+                        }
+                        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, z, work, _params);
+                        }
+                    }
+                }
+                
+                //
+                // return to start of the main loop with new value of I.
+                //
+                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,
+            alglib.xparams _params)
+        {
+            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, _params)!=extschursigntoone(c, _params) )
+                    {
+                        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, _params)*extschursigntoone(c, _params);
+                        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, _params);
+                            a = d+z;
+                            d = d-bcmax/z*bcmis;
+                            
+                            //
+                            // Compute B and the rotation matrix
+                            //
+                            tau = blas.pythag2(c, z, _params);
+                            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, _params);
+                            cs = Math.Sqrt(0.5*(1+Math.Abs(sigma)/tau));
+                            sn = -(p/(tau*cs)*extschursign(1, sigma, _params));
+                            
+                            //
+                            // 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, _params)==extschursigntoone(c, _params) )
+                                    {
+                                        
+                                        //
+                                        // Real eigenvalues: reduce to upper triangular form
+                                        //
+                                        sab = Math.Sqrt(Math.Abs(b));
+                                        sac = Math.Sqrt(Math.Abs(c));
+                                        p = extschursign(sab*sac, c, _params);
+                                        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            if( (double)(b)>=(double)(0) )
+            {
+                result = 1;
+            }
+            else
+            {
+                result = -1;
+            }
+            return result;
+        }
+
+
+    }
+    public class evd
+    {
+        /*************************************************************************
+        This object stores state of the subspace iteration algorithm.
+
+        You should use ALGLIB functions to work with this object.
+        *************************************************************************/
+        public class eigsubspacestate : apobject
+        {
+            public int n;
+            public int k;
+            public int nwork;
+            public int maxits;
+            public double eps;
+            public int eigenvectorsneeded;
+            public int matrixtype;
+            public bool usewarmstart;
+            public bool firstcall;
+            public hqrnd.hqrndstate rs;
+            public bool running;
+            public double[] tau;
+            public double[,] q0;
+            public double[,] qcur;
+            public double[,] qnew;
+            public double[,] znew;
+            public double[,] r;
+            public double[,] rz;
+            public double[,] tz;
+            public double[,] rq;
+            public double[,] dummy;
+            public double[] rw;
+            public double[] tw;
+            public double[] wcur;
+            public double[] wprev;
+            public double[] wrank;
+            public apserv.apbuffers buf;
+            public double[,] x;
+            public double[,] ax;
+            public int requesttype;
+            public int requestsize;
+            public int repiterationscount;
+            public rcommstate rstate;
+            public eigsubspacestate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                rs = new hqrnd.hqrndstate();
+                tau = new double[0];
+                q0 = new double[0,0];
+                qcur = new double[0,0];
+                qnew = new double[0,0];
+                znew = new double[0,0];
+                r = new double[0,0];
+                rz = new double[0,0];
+                tz = new double[0,0];
+                rq = new double[0,0];
+                dummy = new double[0,0];
+                rw = new double[0];
+                tw = new double[0];
+                wcur = new double[0];
+                wprev = new double[0];
+                wrank = new double[0];
+                buf = new apserv.apbuffers();
+                x = new double[0,0];
+                ax = new double[0,0];
+                rstate = new rcommstate();
+            }
+            public override alglib.apobject make_copy()
+            {
+                eigsubspacestate _result = new eigsubspacestate();
+                _result.n = n;
+                _result.k = k;
+                _result.nwork = nwork;
+                _result.maxits = maxits;
+                _result.eps = eps;
+                _result.eigenvectorsneeded = eigenvectorsneeded;
+                _result.matrixtype = matrixtype;
+                _result.usewarmstart = usewarmstart;
+                _result.firstcall = firstcall;
+                _result.rs = (hqrnd.hqrndstate)rs.make_copy();
+                _result.running = running;
+                _result.tau = (double[])tau.Clone();
+                _result.q0 = (double[,])q0.Clone();
+                _result.qcur = (double[,])qcur.Clone();
+                _result.qnew = (double[,])qnew.Clone();
+                _result.znew = (double[,])znew.Clone();
+                _result.r = (double[,])r.Clone();
+                _result.rz = (double[,])rz.Clone();
+                _result.tz = (double[,])tz.Clone();
+                _result.rq = (double[,])rq.Clone();
+                _result.dummy = (double[,])dummy.Clone();
+                _result.rw = (double[])rw.Clone();
+                _result.tw = (double[])tw.Clone();
+                _result.wcur = (double[])wcur.Clone();
+                _result.wprev = (double[])wprev.Clone();
+                _result.wrank = (double[])wrank.Clone();
+                _result.buf = (apserv.apbuffers)buf.make_copy();
+                _result.x = (double[,])x.Clone();
+                _result.ax = (double[,])ax.Clone();
+                _result.requesttype = requesttype;
+                _result.requestsize = requestsize;
+                _result.repiterationscount = repiterationscount;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This object stores state of the subspace iteration algorithm.
+
+        You should use ALGLIB functions to work with this object.
+        *************************************************************************/
+        public class eigsubspacereport : apobject
+        {
+            public int iterationscount;
+            public eigsubspacereport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                eigsubspacereport _result = new eigsubspacereport();
+                _result.iterationscount = iterationscount;
+                return _result;
+            }
+        };
+
+
+
+
+        public const int stepswithintol = 2;
+
+
+        /*************************************************************************
+        This function initializes subspace iteration solver. This solver  is  used
+        to solve symmetric real eigenproblems where just a few (top K) eigenvalues
+        and corresponding eigenvectors is required.
+
+        This solver can be significantly faster than  complete  EVD  decomposition
+        in the following case:
+        * when only just a small fraction  of  top  eigenpairs  of dense matrix is
+          required. When K approaches N, this solver is slower than complete dense
+          EVD
+        * when problem matrix is sparse (and/or is not known explicitly, i.e. only
+          matrix-matrix product can be performed)
+          
+        USAGE (explicit dense/sparse matrix):
+        1. User initializes algorithm state with eigsubspacecreate() call
+        2. [optional] User tunes solver parameters by calling eigsubspacesetcond()
+           or other functions
+        3. User  calls  eigsubspacesolvedense() or eigsubspacesolvesparse() methods,
+           which take algorithm state and 2D array or alglib.sparsematrix object.
+          
+        USAGE (out-of-core mode):
+        1. User initializes algorithm state with eigsubspacecreate() call
+        2. [optional] User tunes solver parameters by calling eigsubspacesetcond()
+           or other functions
+        3. User activates out-of-core mode of  the  solver  and  repeatedly  calls
+           communication functions in a loop like below:
+           > alglib.eigsubspaceoocstart(state)
+           > while alglib.eigsubspaceooccontinue(state) do
+           >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+           >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+           >     [calculate  Y=A*X, with X=R^NxM]
+           >     alglib.eigsubspaceoocsendresult(state, in Y)
+           > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+           
+        INPUT PARAMETERS:
+            N       -   problem dimensionality, N>0
+            K       -   number of top eigenvector to calculate, 0<K<=N.
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+        NOTE: if you solve many similar EVD problems you may  find  it  useful  to
+              reuse previous subspace as warm-start point for new EVD problem.  It
+              can be done with eigsubspacesetwarmstart() function.
+
+          -- ALGLIB --
+             Copyright 16.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void eigsubspacecreate(int n,
+            int k,
+            eigsubspacestate state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(n>0, "EigSubspaceCreate: N<=0");
+            alglib.ap.assert(k>0, "EigSubspaceCreate: K<=0");
+            alglib.ap.assert(k<=n, "EigSubspaceCreate: K>N");
+            eigsubspacecreatebuf(n, k, state, _params);
+        }
+
+
+        /*************************************************************************
+        Buffered version of constructor which aims to reuse  previously  allocated
+        memory as much as possible.
+
+          -- ALGLIB --
+             Copyright 16.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void eigsubspacecreatebuf(int n,
+            int k,
+            eigsubspacestate state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(n>0, "EigSubspaceCreate: N<=0");
+            alglib.ap.assert(k>0, "EigSubspaceCreate: K<=0");
+            alglib.ap.assert(k<=n, "EigSubspaceCreate: K>N");
+            
+            //
+            // Initialize algorithm parameters
+            //
+            state.running = false;
+            state.n = n;
+            state.k = k;
+            state.nwork = Math.Min(Math.Max(2*k, 8), n);
+            state.eigenvectorsneeded = 1;
+            state.usewarmstart = false;
+            state.firstcall = true;
+            eigsubspacesetcond(state, 0.0, 0, _params);
+            
+            //
+            // Allocate temporaries
+            //
+            apserv.rmatrixsetlengthatleast(ref state.x, state.n, state.nwork, _params);
+            apserv.rmatrixsetlengthatleast(ref state.ax, state.n, state.nwork, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets stopping critera for the solver:
+        * error in eigenvector/value allowed by solver
+        * maximum number of iterations to perform
+
+        INPUT PARAMETERS:
+            State       -   solver structure
+            Eps         -   eps>=0,  with non-zero value used to tell solver  that
+                            it can  stop  after  all  eigenvalues  converged  with
+                            error  roughly  proportional  to  eps*MAX(LAMBDA_MAX),
+                            where LAMBDA_MAX is a maximum eigenvalue.
+                            Zero  value  means  that  no  check  for  precision is
+                            performed.
+            MaxIts      -   maxits>=0,  with non-zero value used  to  tell  solver
+                            that it can stop after maxits  steps  (no  matter  how
+                            precise current estimate is)
+
+        NOTE: passing  eps=0  and  maxits=0  results  in  automatic  selection  of
+              moderate eps as stopping criteria (1.0E-6 in current implementation,
+              but it may change without notice).
+              
+        NOTE: very small values of eps are possible (say, 1.0E-12),  although  the
+              larger problem you solve (N and/or K), the  harder  it  is  to  find
+              precise eigenvectors because rounding errors tend to accumulate.
+              
+        NOTE: passing non-zero eps results in  some performance  penalty,  roughly
+              equal to 2N*(2K)^2 FLOPs per iteration. These additional computations
+              are required in order to estimate current error in  eigenvalues  via
+              Rayleigh-Ritz process.
+              Most of this additional time is  spent  in  construction  of  ~2Kx2K
+              symmetric  subproblem  whose  eigenvalues  are  checked  with  exact
+              eigensolver.
+              This additional time is negligible if you search for eigenvalues  of
+              the large dense matrix, but may become noticeable on  highly  sparse
+              EVD problems, where cost of matrix-matrix product is low.
+              If you set eps to exactly zero,  Rayleigh-Ritz  phase  is completely
+              turned off.
+
+          -- ALGLIB --
+             Copyright 16.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void eigsubspacesetcond(eigsubspacestate state,
+            double eps,
+            int maxits,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(!state.running, "EigSubspaceSetCond: solver is already running");
+            alglib.ap.assert(math.isfinite(eps) && (double)(eps)>=(double)(0), "EigSubspaceSetCond: Eps<0 or NAN/INF");
+            alglib.ap.assert(maxits>=0, "EigSubspaceSetCond: MaxIts<0");
+            if( (double)(eps)==(double)(0) && maxits==0 )
+            {
+                eps = 1.0E-6;
+            }
+            state.eps = eps;
+            state.maxits = maxits;
+        }
+
+
+        /*************************************************************************
+        This function sets warm-start mode of the solver: next call to the  solver
+        will reuse previous subspace as warm-start  point.  It  can  significantly
+        speed-up convergence when you solve many similar eigenproblems.
+
+        INPUT PARAMETERS:
+            State       -   solver structure
+            UseWarmStart-   either True or False
+
+          -- ALGLIB --
+             Copyright 12.11.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void eigsubspacesetwarmstart(eigsubspacestate state,
+            bool usewarmstart,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(!state.running, "EigSubspaceSetWarmStart: solver is already running");
+            state.usewarmstart = usewarmstart;
+        }
+
+
+        /*************************************************************************
+        This  function  initiates  out-of-core  mode  of  subspace eigensolver. It
+        should be used in conjunction with other out-of-core-related functions  of
+        this subspackage in a loop like below:
+
+        > alglib.eigsubspaceoocstart(state)
+        > while alglib.eigsubspaceooccontinue(state) do
+        >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+        >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+        >     [calculate  Y=A*X, with X=R^NxM]
+        >     alglib.eigsubspaceoocsendresult(state, in Y)
+        > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+        INPUT PARAMETERS:
+            State       -   solver object
+            MType       -   matrix type:
+                            * 0 for real  symmetric  matrix  (solver  assumes that
+                              matrix  being   processed  is  symmetric;  symmetric
+                              direct eigensolver is used for  smaller  subproblems
+                              arising during solution of larger "full" task)
+                            Future versions of ALGLIB may  introduce  support  for
+                            other  matrix   types;   for   now,   only   symmetric
+                            eigenproblems are supported.
+
+
+          -- ALGLIB --
+             Copyright 16.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void eigsubspaceoocstart(eigsubspacestate state,
+            int mtype,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(!state.running, "EigSubspaceStart: solver is already running");
+            alglib.ap.assert(mtype==0, "EigSubspaceStart: incorrect mtype parameter");
+            state.rstate.ia = new int[7+1];
+            state.rstate.ra = new double[1+1];
+            state.rstate.stage = -1;
+            clearrfields(state, _params);
+            state.running = true;
+            state.matrixtype = mtype;
+        }
+
+
+        /*************************************************************************
+        This function performs subspace iteration  in  the  out-of-core  mode.  It
+        should be used in conjunction with other out-of-core-related functions  of
+        this subspackage in a loop like below:
+
+        > alglib.eigsubspaceoocstart(state)
+        > while alglib.eigsubspaceooccontinue(state) do
+        >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+        >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+        >     [calculate  Y=A*X, with X=R^NxM]
+        >     alglib.eigsubspaceoocsendresult(state, in Y)
+        > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+
+          -- ALGLIB --
+             Copyright 16.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool eigsubspaceooccontinue(eigsubspacestate state,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            alglib.ap.assert(state.running, "EigSubspaceContinue: solver is not running");
+            result = eigsubspaceiteration(state, _params);
+            state.running = result;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function is used to retrieve information  about  out-of-core  request
+        sent by solver to user code: request type (current version  of  the solver
+        sends only requests for matrix-matrix products) and request size (size  of
+        the matrices being multiplied).
+
+        This function returns just request metrics; in order  to  get contents  of
+        the matrices being multiplied, use eigsubspaceoocgetrequestdata().
+
+        It should be used in conjunction with other out-of-core-related  functions
+        of this subspackage in a loop like below:
+
+        > alglib.eigsubspaceoocstart(state)
+        > while alglib.eigsubspaceooccontinue(state) do
+        >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+        >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+        >     [calculate  Y=A*X, with X=R^NxM]
+        >     alglib.eigsubspaceoocsendresult(state, in Y)
+        > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+        INPUT PARAMETERS:
+            State           -   solver running in out-of-core mode
+            
+        OUTPUT PARAMETERS:
+            RequestType     -   type of the request to process:
+                                * 0 - for matrix-matrix product A*X, with A  being
+                                  NxN matrix whose eigenvalues/vectors are needed,
+                                  and X being NxREQUESTSIZE one which is  returned
+                                  by the eigsubspaceoocgetrequestdata().
+            RequestSize     -   size of the X matrix (number of columns),  usually
+                                it is several times larger than number of  vectors
+                                K requested by user.
+
+
+          -- ALGLIB --
+             Copyright 16.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void eigsubspaceoocgetrequestinfo(eigsubspacestate state,
+            ref int requesttype,
+            ref int requestsize,
+            alglib.xparams _params)
+        {
+            requesttype = 0;
+            requestsize = 0;
+
+            alglib.ap.assert(state.running, "EigSubspaceOOCGetRequestInfo: solver is not running");
+            requesttype = state.requesttype;
+            requestsize = state.requestsize;
+        }
+
+
+        /*************************************************************************
+        This function is used to retrieve information  about  out-of-core  request
+        sent by solver to user code: matrix X (array[N,RequestSize) which have  to
+        be multiplied by out-of-core matrix A in a product A*X.
+
+        This function returns just request data; in order to get size of  the data
+        prior to processing requestm, use eigsubspaceoocgetrequestinfo().
+
+        It should be used in conjunction with other out-of-core-related  functions
+        of this subspackage in a loop like below:
+
+        > alglib.eigsubspaceoocstart(state)
+        > while alglib.eigsubspaceooccontinue(state) do
+        >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+        >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+        >     [calculate  Y=A*X, with X=R^NxM]
+        >     alglib.eigsubspaceoocsendresult(state, in Y)
+        > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+        INPUT PARAMETERS:
+            State           -   solver running in out-of-core mode
+            X               -   possibly  preallocated   storage;  reallocated  if
+                                needed, left unchanged, if large enough  to  store
+                                request data.
+            
+        OUTPUT PARAMETERS:
+            X               -   array[N,RequestSize] or larger, leading  rectangle
+                                is filled with dense matrix X.
+
+
+          -- ALGLIB --
+             Copyright 16.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void eigsubspaceoocgetrequestdata(eigsubspacestate state,
+            ref double[,] x,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(state.running, "EigSubspaceOOCGetRequestInfo: solver is not running");
+            apserv.rmatrixsetlengthatleast(ref x, state.n, state.requestsize, _params);
+            for(i=0; i<=state.n-1; i++)
+            {
+                for(j=0; j<=state.requestsize-1; j++)
+                {
+                    x[i,j] = state.x[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function is used to send user reply to out-of-core  request  sent  by
+        solver. Usually it is product A*X for returned by solver matrix X.
+
+        It should be used in conjunction with other out-of-core-related  functions
+        of this subspackage in a loop like below:
+
+        > alglib.eigsubspaceoocstart(state)
+        > while alglib.eigsubspaceooccontinue(state) do
+        >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+        >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+        >     [calculate  Y=A*X, with X=R^NxM]
+        >     alglib.eigsubspaceoocsendresult(state, in Y)
+        > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+        INPUT PARAMETERS:
+            State           -   solver running in out-of-core mode
+            AX              -   array[N,RequestSize] or larger, leading  rectangle
+                                is filled with product A*X.
+
+
+          -- ALGLIB --
+             Copyright 16.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void eigsubspaceoocsendresult(eigsubspacestate state,
+            double[,] ax,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(state.running, "EigSubspaceOOCGetRequestInfo: solver is not running");
+            for(i=0; i<=state.n-1; i++)
+            {
+                for(j=0; j<=state.requestsize-1; j++)
+                {
+                    state.ax[i,j] = ax[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This  function  finalizes out-of-core  mode  of  subspace eigensolver.  It
+        should be used in conjunction with other out-of-core-related functions  of
+        this subspackage in a loop like below:
+
+        > alglib.eigsubspaceoocstart(state)
+        > while alglib.eigsubspaceooccontinue(state) do
+        >     alglib.eigsubspaceoocgetrequestinfo(state, out RequestType, out M)
+        >     alglib.eigsubspaceoocgetrequestdata(state, out X)
+        >     [calculate  Y=A*X, with X=R^NxM]
+        >     alglib.eigsubspaceoocsendresult(state, in Y)
+        > alglib.eigsubspaceoocstop(state, out W, out Z, out Report)
+
+        INPUT PARAMETERS:
+            State       -   solver state
+            
+        OUTPUT PARAMETERS:
+            W           -   array[K], depending on solver settings:
+                            * top  K  eigenvalues ordered  by  descending   -   if
+                              eigenvectors are returned in Z
+                            * zeros - if invariant subspace is returned in Z
+            Z           -   array[N,K], depending on solver settings either:
+                            * matrix of eigenvectors found
+                            * orthogonal basis of K-dimensional invariant subspace
+            Rep         -   report with additional parameters
+
+          -- ALGLIB --
+             Copyright 16.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void eigsubspaceoocstop(eigsubspacestate state,
+            ref double[] w,
+            ref double[,] z,
+            eigsubspacereport rep,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int k = 0;
+            int i = 0;
+            int j = 0;
+
+            w = new double[0];
+            z = new double[0,0];
+
+            alglib.ap.assert(!state.running, "EigSubspaceStop: solver is still running");
+            n = state.n;
+            k = state.k;
+            w = new double[k];
+            z = new double[n, k];
+            for(i=0; i<=k-1; i++)
+            {
+                w[i] = state.rw[i];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=k-1; j++)
+                {
+                    z[i,j] = state.rq[i,j];
+                }
+            }
+            rep.iterationscount = state.repiterationscount;
+        }
+
+
+        /*************************************************************************
+        This  function runs eigensolver for dense NxN symmetric matrix A, given by
+        upper or lower triangle.
+
+        This function can not process nonsymmetric matrices.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+          
+        INPUT PARAMETERS:
+            State       -   solver state
+            A           -   array[N,N], symmetric NxN matrix given by one  of  its
+                            triangles
+            IsUpper     -   whether upper or lower triangle of  A  is  given  (the
+                            other one is not referenced at all).
+            
+        OUTPUT PARAMETERS:
+            W           -   array[K], top  K  eigenvalues ordered  by   descending
+                            of their absolute values
+            Z           -   array[N,K], matrix of eigenvectors found
+            Rep         -   report with additional parameters
+            
+        NOTE: internally this function allocates a copy of NxN dense A. You should
+              take it into account when working with very large matrices occupying
+              almost all RAM.
+
+          -- ALGLIB --
+             Copyright 16.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void eigsubspacesolvedenses(eigsubspacestate state,
+            double[,] a,
+            bool isupper,
+            ref double[] w,
+            ref double[,] z,
+            eigsubspacereport rep,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int m = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            double[,] acopy = new double[0,0];
+
+            w = new double[0];
+            z = new double[0,0];
+
+            alglib.ap.assert(!state.running, "EigSubspaceSolveDenseS: solver is still running");
+            n = state.n;
+            
+            //
+            // Allocate copy of A, copy one triangle to another
+            //
+            acopy = new double[n, n];
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=i; j<=n-1; j++)
+                {
+                    if( isupper )
+                    {
+                        v = a[i,j];
+                    }
+                    else
+                    {
+                        v = a[j,i];
+                    }
+                    acopy[i,j] = v;
+                    acopy[j,i] = v;
+                }
+            }
+            
+            //
+            // Start iterations
+            //
+            state.matrixtype = 0;
+            state.rstate.ia = new int[7+1];
+            state.rstate.ra = new double[1+1];
+            state.rstate.stage = -1;
+            clearrfields(state, _params);
+            while( eigsubspaceiteration(state, _params) )
+            {
+                
+                //
+                // Calculate A*X with RMatrixGEMM
+                //
+                alglib.ap.assert(state.requesttype==0, "EigSubspaceSolveDense: integrity check failed");
+                alglib.ap.assert(state.requestsize>0, "EigSubspaceSolveDense: integrity check failed");
+                m = state.requestsize;
+                ablas.rmatrixgemm(n, m, n, 1.0, acopy, 0, 0, 0, state.x, 0, 0, 0, 0.0, state.ax, 0, 0, _params);
+            }
+            k = state.k;
+            w = new double[k];
+            z = new double[n, k];
+            for(i=0; i<=k-1; i++)
+            {
+                w[i] = state.rw[i];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=k-1; j++)
+                {
+                    z[i,j] = state.rq[i,j];
+                }
+            }
+            rep.iterationscount = state.repiterationscount;
+        }
+
+
+        /*************************************************************************
+        This  function runs eigensolver for dense NxN symmetric matrix A, given by
+        upper or lower triangle.
+
+        This function can not process nonsymmetric matrices.
+
+        INPUT PARAMETERS:
+            State       -   solver state
+            A           -   NxN symmetric matrix given by one of its triangles
+            IsUpper     -   whether upper or lower triangle of  A  is  given  (the
+                            other one is not referenced at all).
+            
+        OUTPUT PARAMETERS:
+            W           -   array[K], top  K  eigenvalues ordered  by   descending
+                            of their absolute values
+            Z           -   array[N,K], matrix of eigenvectors found
+            Rep         -   report with additional parameters
+
+          -- ALGLIB --
+             Copyright 16.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void eigsubspacesolvesparses(eigsubspacestate state,
+            sparse.sparsematrix a,
+            bool isupper,
+            ref double[] w,
+            ref double[,] z,
+            eigsubspacereport rep,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+
+            w = new double[0];
+            z = new double[0,0];
+
+            alglib.ap.assert(!state.running, "EigSubspaceSolveSparseS: solver is still running");
+            n = state.n;
+            state.matrixtype = 0;
+            state.rstate.ia = new int[7+1];
+            state.rstate.ra = new double[1+1];
+            state.rstate.stage = -1;
+            clearrfields(state, _params);
+            while( eigsubspaceiteration(state, _params) )
+            {
+                alglib.ap.assert(state.requesttype==0, "EigSubspaceSolveDense: integrity check failed");
+                alglib.ap.assert(state.requestsize>0, "EigSubspaceSolveDense: integrity check failed");
+                sparse.sparsesmm(a, isupper, state.x, state.requestsize, ref state.ax, _params);
+            }
+            k = state.k;
+            w = new double[k];
+            z = new double[n, k];
+            for(i=0; i<=k-1; i++)
+            {
+                w[i] = state.rw[i];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=k-1; j++)
+                {
+                    z[i,j] = state.rq[i,j];
+                }
+            }
+            rep.iterationscount = state.repiterationscount;
+        }
+
+
+        /*************************************************************************
+        Internal r-comm function.
+
+          -- ALGLIB --
+             Copyright 16.01.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool eigsubspaceiteration(eigsubspacestate state,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int nwork = 0;
+            int k = 0;
+            int cnt = 0;
+            int i = 0;
+            int i1 = 0;
+            int j = 0;
+            double vv = 0;
+            double v = 0;
+            int convcnt = 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];
+                nwork = state.rstate.ia[1];
+                k = state.rstate.ia[2];
+                cnt = state.rstate.ia[3];
+                i = state.rstate.ia[4];
+                i1 = state.rstate.ia[5];
+                j = state.rstate.ia[6];
+                convcnt = state.rstate.ia[7];
+                vv = state.rstate.ra[0];
+                v = state.rstate.ra[1];
+            }
+            else
+            {
+                n = 359;
+                nwork = -58;
+                k = -919;
+                cnt = -909;
+                i = 81;
+                i1 = 255;
+                j = 74;
+                convcnt = -788;
+                vv = 809;
+                v = 205;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            
+            //
+            // Routine body
+            //
+            n = state.n;
+            k = state.k;
+            nwork = state.nwork;
+            
+            //
+            // Initialize RNG. Deterministic initialization (with fixed
+            // seed) is required because we need deterministic behavior
+            // of the entire solver.
+            //
+            hqrnd.hqrndseed(453, 463664, state.rs, _params);
+            
+            //
+            // Prepare iteration
+            // Initialize QNew with random orthogonal matrix (or reuse its previous value).
+            //
+            state.repiterationscount = 0;
+            apserv.rmatrixsetlengthatleast(ref state.qcur, nwork, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.qnew, nwork, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.znew, nwork, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.wcur, nwork, _params);
+            apserv.rvectorsetlengthatleast(ref state.wprev, nwork, _params);
+            apserv.rvectorsetlengthatleast(ref state.wrank, nwork, _params);
+            apserv.rmatrixsetlengthatleast(ref state.x, n, nwork, _params);
+            apserv.rmatrixsetlengthatleast(ref state.ax, n, nwork, _params);
+            apserv.rmatrixsetlengthatleast(ref state.rq, n, k, _params);
+            apserv.rvectorsetlengthatleast(ref state.rw, k, _params);
+            apserv.rmatrixsetlengthatleast(ref state.rz, nwork, k, _params);
+            apserv.rmatrixsetlengthatleast(ref state.r, nwork, nwork, _params);
+            for(i=0; i<=nwork-1; i++)
+            {
+                state.wprev[i] = -1.0;
+            }
+            if( !state.usewarmstart || state.firstcall )
+            {
+                
+                //
+                // Use Q0 (either no warm start request, or warm start was
+                // requested by user - but it is first call).
+                // 
+                //
+                if( state.firstcall )
+                {
+                    
+                    //
+                    // First call, generate Q0
+                    //
+                    for(i=0; i<=nwork-1; i++)
+                    {
+                        for(j=0; j<=n-1; j++)
+                        {
+                            state.znew[i,j] = hqrnd.hqrnduniformr(state.rs, _params)-0.5;
+                        }
+                    }
+                    ortfac.rmatrixlq(ref state.znew, nwork, n, ref state.tau, _params);
+                    ortfac.rmatrixlqunpackq(state.znew, nwork, n, state.tau, nwork, ref state.q0, _params);
+                    state.firstcall = false;
+                }
+                ablas.rmatrixcopy(nwork, n, state.q0, 0, 0, state.qnew, 0, 0, _params);
+            }
+            
+            //
+            // Start iteration
+            //
+            state.repiterationscount = 0;
+            convcnt = 0;
+        lbl_1:
+            if( !((state.maxits==0 || state.repiterationscount<state.maxits) && convcnt<stepswithintol) )
+            {
+                goto lbl_2;
+            }
+            
+            //
+            // Update QCur := QNew
+            //
+            // Calculate A*Q'
+            //
+            ablas.rmatrixcopy(nwork, n, state.qnew, 0, 0, state.qcur, 0, 0, _params);
+            ablas.rmatrixtranspose(nwork, n, state.qcur, 0, 0, state.x, 0, 0, _params);
+            clearrfields(state, _params);
+            state.requesttype = 0;
+            state.requestsize = nwork;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            
+            //
+            // Perform Rayleigh-Ritz step to estimate convergence of diagonal eigenvalues
+            //
+            if( (double)(state.eps)>(double)(0) )
+            {
+                alglib.ap.assert(state.matrixtype==0, "integrity check failed");
+                apserv.rmatrixsetlengthatleast(ref state.r, nwork, nwork, _params);
+                ablas.rmatrixgemm(nwork, nwork, n, 1.0, state.qcur, 0, 0, 0, state.ax, 0, 0, 0, 0.0, state.r, 0, 0, _params);
+                if( !smatrixevd(state.r, nwork, 0, true, ref state.wcur, ref state.dummy, _params) )
+                {
+                    alglib.ap.assert(false, "EigSubspace: direct eigensolver failed to converge");
+                }
+                for(j=0; j<=nwork-1; j++)
+                {
+                    state.wrank[j] = Math.Abs(state.wcur[j]);
+                }
+                basicstatops.rankxuntied(state.wrank, nwork, state.buf, _params);
+                v = 0;
+                vv = 0;
+                for(j=0; j<=nwork-1; j++)
+                {
+                    if( (double)(state.wrank[j])>=(double)(nwork-k) )
+                    {
+                        v = Math.Max(v, Math.Abs(state.wcur[j]-state.wprev[j]));
+                        vv = Math.Max(vv, Math.Abs(state.wcur[j]));
+                    }
+                }
+                if( (double)(vv)==(double)(0) )
+                {
+                    vv = 1;
+                }
+                if( (double)(v)<=(double)(state.eps*vv) )
+                {
+                    apserv.inc(ref convcnt, _params);
+                }
+                else
+                {
+                    convcnt = 0;
+                }
+                for(j=0; j<=nwork-1; j++)
+                {
+                    state.wprev[j] = state.wcur[j];
+                }
+            }
+            
+            //
+            // QR renormalization and update of QNew
+            //
+            ablas.rmatrixtranspose(n, nwork, state.ax, 0, 0, state.znew, 0, 0, _params);
+            ortfac.rmatrixlq(ref state.znew, nwork, n, ref state.tau, _params);
+            ortfac.rmatrixlqunpackq(state.znew, nwork, n, state.tau, nwork, ref state.qnew, _params);
+            
+            //
+            // Update iteration index
+            //
+            state.repiterationscount = state.repiterationscount+1;
+            goto lbl_1;
+        lbl_2:
+            
+            //
+            // Perform Rayleigh-Ritz step: find true eigenpairs in NWork-dimensional
+            // subspace.
+            //
+            alglib.ap.assert(state.matrixtype==0, "integrity check failed");
+            alglib.ap.assert(state.eigenvectorsneeded==1);
+            ablas.rmatrixgemm(nwork, nwork, n, 1.0, state.qcur, 0, 0, 0, state.ax, 0, 0, 0, 0.0, state.r, 0, 0, _params);
+            if( !smatrixevd(state.r, nwork, 1, true, ref state.tw, ref state.tz, _params) )
+            {
+                alglib.ap.assert(false, "EigSubspace: direct eigensolver failed to converge");
+            }
+            
+            //
+            // Reorder eigenpairs according to their absolute magnitude, select
+            // K top ones. This reordering algorithm is very inefficient and has
+            // O(NWork*K) running time, but it is still faster than other parts
+            // of the solver, so we may use it.
+            //
+            // Then, we transform RZ to RQ (full N-dimensional representation).
+            // After this part is done, RW and RQ contain solution.
+            //
+            for(j=0; j<=nwork-1; j++)
+            {
+                state.wrank[j] = Math.Abs(state.tw[j]);
+            }
+            basicstatops.rankxuntied(state.wrank, nwork, state.buf, _params);
+            cnt = 0;
+            for(i=nwork-1; i>=nwork-k; i--)
+            {
+                for(i1=0; i1<=nwork-1; i1++)
+                {
+                    if( (double)(state.wrank[i1])==(double)(i) )
+                    {
+                        alglib.ap.assert(cnt<k, "EigSubspace: integrity check failed");
+                        state.rw[cnt] = state.tw[i1];
+                        for(j=0; j<=nwork-1; j++)
+                        {
+                            state.rz[j,cnt] = state.tz[j,i1];
+                        }
+                        cnt = cnt+1;
+                    }
+                }
+            }
+            alglib.ap.assert(cnt==k, "EigSubspace: integrity check failed");
+            ablas.rmatrixgemm(n, k, nwork, 1.0, state.qcur, 0, 0, 1, state.rz, 0, 0, 0, 0.0, state.rq, 0, 0, _params);
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = n;
+            state.rstate.ia[1] = nwork;
+            state.rstate.ia[2] = k;
+            state.rstate.ia[3] = cnt;
+            state.rstate.ia[4] = i;
+            state.rstate.ia[5] = i1;
+            state.rstate.ia[6] = j;
+            state.rstate.ia[7] = convcnt;
+            state.rstate.ra[0] = vv;
+            state.rstate.ra[1] = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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 hasn't 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            if( zneeded==1 )
+            {
+                ortfac.smatrixtdunpackq(a, n, isupper, tau, ref z, _params);
+            }
+            result = smatrixtdevd(ref d, e, n, zneeded, ref z, _params);
+            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
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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 hasn't 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            if( zneeded==1 )
+            {
+                ortfac.smatrixtdunpackq(a, n, isupper, tau, ref z, _params);
+            }
+            result = smatrixtdevdr(ref w, e, n, zneeded, b1, b2, ref m, ref z, _params);
+            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 hasn't 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            if( zneeded==1 )
+            {
+                ortfac.smatrixtdunpackq(a, n, isupper, tau, ref z, _params);
+            }
+            result = smatrixtdevdi(ref w, e, n, zneeded, i1, i2, ref z, _params);
+            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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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 hasn't 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,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            complex[] tau = new complex[0];
+            double[] e = new double[0];
+            double[,] t = new double[0,0];
+            double[,] qz = new double[0,0];
+            complex[,] q = new complex[0,0];
+            int i = 0;
+            int j = 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, _params);
+            if( zneeded==1 )
+            {
+                ortfac.hmatrixtdunpackq(a, n, isupper, tau, ref q, _params);
+                zneeded = 2;
+            }
+            
+            //
+            // TDEVD
+            //
+            result = smatrixtdevd(ref d, e, n, zneeded, ref t, _params);
+            
+            //
+            // Eigenvectors are needed
+            // Calculate Z = Q*T = Re(Q)*T + i*Im(Q)*T
+            //
+            if( result && zneeded!=0 )
+            {
+                z = new complex[n, n];
+                qz = new double[n, 2*n];
+                
+                //
+                // Calculate Re(Q)*T
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        qz[i,j] = q[i,j].x;
+                    }
+                }
+                ablas.rmatrixgemm(n, n, n, 1.0, qz, 0, 0, 0, t, 0, 0, 0, 0.0, qz, 0, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        z[i,j].x = qz[i,n+j];
+                    }
+                }
+                
+                //
+                // Calculate Im(Q)*T
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        qz[i,j] = q[i,j].y;
+                    }
+                }
+                ablas.rmatrixgemm(n, n, n, 1.0, qz, 0, 0, 0, t, 0, 0, 0, 0.0, qz, 0, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        z[i,j].y = qz[i,n+j];
+                    }
+                }
+            }
+            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 hasn't 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            if( zneeded==1 )
+            {
+                ortfac.hmatrixtdunpackq(a, n, isupper, tau, ref q, _params);
+                zneeded = 2;
+            }
+            
+            //
+            // Bisection and inverse iteration
+            //
+            result = smatrixtdevdr(ref w, e, n, zneeded, b1, b2, ref m, ref t, _params);
+            
+            //
+            // 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 hasn't 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            if( zneeded==1 )
+            {
+                ortfac.hmatrixtdunpackq(a, n, isupper, tau, ref q, _params);
+                zneeded = 2;
+            }
+            
+            //
+            // Bisection and inverse iteration
+            //
+            result = smatrixtdevdi(ref w, e, n, zneeded, i1, i2, ref t, _params);
+            
+            //
+            // 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.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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 hasn't 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,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            double[] d1 = new double[0];
+            double[] e1 = new double[0];
+            double[] ex = new double[0];
+            double[,] z1 = new double[0,0];
+            int i = 0;
+            int j = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            e = (double[])e.Clone();
+
+            alglib.ap.assert(n>=1, "SMatrixTDEVD: N<=0");
+            alglib.ap.assert(zneeded>=0 && zneeded<=3, "SMatrixTDEVD: incorrect ZNeeded");
+            result = false;
+            
+            //
+            // Preprocess Z: make ZNeeded equal to 0, 1 or 3.
+            // Ensure that memory for Z is allocated.
+            //
+            if( zneeded==2 )
+            {
+                
+                //
+                // Load identity to Z
+                //
+                apserv.rmatrixsetlengthatleast(ref z, n, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        z[i,j] = 0.0;
+                    }
+                    z[i,i] = 1.0;
+                }
+                zneeded = 1;
+            }
+            if( zneeded==3 )
+            {
+                
+                //
+                // Allocate memory
+                //
+                apserv.rmatrixsetlengthatleast(ref z, 1, n, _params);
+            }
+            
+            //
+            // Try to solve problem with MKL
+            //
+            ex = new double[n];
+            for(i=0; i<=n-2; i++)
+            {
+                ex[i] = e[i];
+            }
+            if( ablasmkl.smatrixtdevdmkl(d, ex, n, zneeded, z, ref result, _params) )
+            {
+                return result;
+            }
+            
+            //
+            // 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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+                if( !result || m==0 )
+                {
+                    m = 0;
+                    return result;
+                }
+                internaldstein(n, d1, e1, m, w, iblock, isplit, ref z2, ref ifail, ref cr, _params);
+                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, _params);
+                if( !result || m==0 )
+                {
+                    m = 0;
+                    return result;
+                }
+                internaldstein(n, d1, e1, m, w, iblock, isplit, ref z2, ref ifail, ref cr, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+                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, _params);
+                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, _params);
+                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, _params);
+                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 (unsymmetric) matrix
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            double[,] a1 = new double[0,0];
+            double[,] vl1 = new double[0,0];
+            double[,] vr1 = new double[0,0];
+            double[,] s1 = new double[0,0];
+            double[,] s = new double[0,0];
+            double[,] dummy = new double[0,0];
+            double[] wr1 = new double[0];
+            double[] wi1 = new double[0];
+            double[] tau = new double[0];
+            int i = 0;
+            int info = 0;
+            bool[] sel1 = new bool[0];
+            int m1 = 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, "RMatrixEVD: incorrect VNeeded!");
+            if( vneeded==0 )
+            {
+                
+                //
+                // Eigen values only
+                //
+                ortfac.rmatrixhessenberg(ref a, n, ref tau, _params);
+                hsschur.rmatrixinternalschurdecomposition(a, n, 0, 0, ref wr, ref wi, ref dummy, ref info, _params);
+                result = info==0;
+                return result;
+            }
+            
+            //
+            // Eigen values and vectors
+            //
+            ortfac.rmatrixhessenberg(ref a, n, ref tau, _params);
+            ortfac.rmatrixhessenbergunpackq(a, n, tau, ref s, _params);
+            hsschur.rmatrixinternalschurdecomposition(a, n, 1, 1, ref wr, ref wi, ref s, ref info, _params);
+            result = info==0;
+            if( !result )
+            {
+                return result;
+            }
+            if( vneeded==1 || vneeded==3 )
+            {
+                vr = new double[n, n];
+                for(i=0; i<=n-1; i++)
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        vr[i,i_] = s[i,i_];
+                    }
+                }
+            }
+            if( vneeded==2 || vneeded==3 )
+            {
+                vl = new double[n, n];
+                for(i=0; i<=n-1; i++)
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        vl[i,i_] = s[i,i_];
+                    }
+                }
+            }
+            rmatrixinternaltrevc(a, n, vneeded, 1, sel1, ref vl, ref vr, ref m1, ref info, _params);
+            result = info==0;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrfields(eigsubspacestate state,
+            alglib.xparams _params)
+        {
+            state.requesttype = -1;
+            state.requestsize = -1;
+        }
+
+
+        private static bool tridiagonalevd(ref double[] d,
+            double[] e,
+            int n,
+            int zneeded,
+            ref double[,] z,
+            alglib.xparams _params)
+        {
+            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 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, _params);
+            }
+            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, _params);
+                                    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, z, wtemp, _params);
+                                    }
+                                    else
+                                    {
+                                        rotations.applyrotationsfromtheleft(false, l, l+1, 1, zrows, workc, works, z, wtemp, _params);
+                                    }
+                                }
+                                else
+                                {
+                                    tdevde2(d[l], e[l], d[l+1], ref rt1, ref rt2, _params);
+                                }
+                                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, _params);
+                            g = d[m]-p+e[l]/(g+tdevdextsign(r, g, _params));
+                            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, _params);
+                                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, z, wtemp, _params);
+                                }
+                                else
+                                {
+                                    rotations.applyrotationsfromtheleft(false, l, m, 1, zrows, workc, works, z, wtemp, _params);
+                                }
+                            }
+                            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, _params);
+                                    work1[m] = c;
+                                    work2[m] = s;
+                                    workc[1] = c;
+                                    works[1] = s;
+                                    if( !wastranspose )
+                                    {
+                                        rotations.applyrotationsfromtheright(true, 1, zrows, l-1, l, workc, works, z, wtemp, _params);
+                                    }
+                                    else
+                                    {
+                                        rotations.applyrotationsfromtheleft(true, l-1, l, 1, zrows, workc, works, z, wtemp, _params);
+                                    }
+                                }
+                                else
+                                {
+                                    tdevde2(d[l-1], e[l-1], d[l], ref rt1, ref rt2, _params);
+                                }
+                                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, _params);
+                            g = d[m]-p+e[l-1]/(g+tdevdextsign(r, g, _params));
+                            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, _params);
+                                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 )
+                            {
+                                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, z, wtemp, _params);
+                                }
+                                else
+                                {
+                                    rotations.applyrotationsfromtheleft(true, m, l, 1, zrows, workc, works, z, wtemp, _params);
+                                }
+                            }
+                            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, _params);
+                    }
+                    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, _params);
+                }
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+                    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, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            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;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+            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;
+
+            hqrnd.hqrndseed(346436, 2434, rs, _params);
+            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*hqrnd.hqrnduniformr(rs, _params)-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, _params);
+                        
+                        //
+                        // 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, _params);
+                            
+                            //
+                            // 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];
+                                        }
+                                        apserv.touchint(ref i2, _params);
+                                    }
+                                }
+                            }
+                            
+                            //
+                            // Check the infinity norm of the iterate.
+                            //
+                            jmax = blas.vectoridxabsmax(work1, 1, blksiz, _params);
+                            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, _params);
+                        jmax = blas.vectoridxabsmax(work1, 1, blksiz, _params);
+                        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 rmatrixinternaltrevc(double[,] t,
+            int n,
+            int side,
+            int howmny,
+            bool[] vselect,
+            ref double[,] vl,
+            ref double[,] vr,
+            ref int m,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double[,] t1 = new double[0,0];
+            double[,] vl1 = new double[0,0];
+            double[,] vr1 = new double[0,0];
+            bool[] vselect1 = new bool[0];
+
+            vselect = (bool[])vselect.Clone();
+            m = 0;
+            info = 0;
+
+            
+            //
+            // Allocate VL/VR, if needed
+            //
+            if( howmny==2 || howmny==3 )
+            {
+                if( side==1 || side==3 )
+                {
+                    apserv.rmatrixsetlengthatleast(ref vr, n, n, _params);
+                }
+                if( side==2 || side==3 )
+                {
+                    apserv.rmatrixsetlengthatleast(ref vl, n, n, _params);
+                }
+            }
+            
+            //
+            // Try to use MKL kernel
+            //
+            if( ablasmkl.rmatrixinternaltrevcmkl(t, n, side, howmny, vl, vr, ref m, ref info, _params) )
+            {
+                return;
+            }
+            
+            //
+            // ALGLIB version
+            //
+            t1 = new double[n+1, n+1];
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    t1[i+1,j+1] = t[i,j];
+                }
+            }
+            if( howmny==3 )
+            {
+                vselect1 = new bool[n+1];
+                for(i=0; i<=n-1; i++)
+                {
+                    vselect1[1+i] = vselect[i];
+                }
+            }
+            if( (side==2 || side==3) && howmny==1 )
+            {
+                vl1 = new double[n+1, n+1];
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        vl1[i+1,j+1] = vl[i,j];
+                    }
+                }
+            }
+            if( (side==1 || side==3) && howmny==1 )
+            {
+                vr1 = new double[n+1, n+1];
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        vr1[i+1,j+1] = vr[i,j];
+                    }
+                }
+            }
+            internaltrevc(t1, n, side, howmny, vselect1, ref vl1, ref vr1, ref m, ref info, _params);
+            if( side!=1 )
+            {
+                apserv.rmatrixsetlengthatleast(ref vl, n, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        vl[i,j] = vl1[i+1,j+1];
+                    }
+                }
+            }
+            if( side!=2 )
+            {
+                apserv.rmatrixsetlengthatleast(ref vr, n, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        vr[i,j] = vr1[i+1,j+1];
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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 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;
+            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, _params);
+                                    
+                                    //
+                                    // 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, _params);
+                                    
+                                    //
+                                    // 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, _params);
+                                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], _params);
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        vr[i_,ki] = temp[i_];
+                                    }
+                                }
+                                ii = blas.columnidxabsmax(vr, 1, n, ki, _params);
+                                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, _params);
+                                    
+                                    //
+                                    // 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, _params);
+                                    
+                                    //
+                                    // 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], _params);
+                                    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], _params);
+                                    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, _params);
+                                    
+                                    //
+                                    // 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, _params);
+                                    
+                                    //
+                                    // 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, _params);
+                                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], _params);
+                                    for(i_=1; i_<=n;i_++)
+                                    {
+                                        vl[i_,ki] = temp[i_];
+                                    }
+                                }
+                                ii = blas.columnidxabsmax(vl, 1, n, ki, _params);
+                                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, _params);
+                                    
+                                    //
+                                    // 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, _params);
+                                    
+                                    //
+                                    // 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], _params);
+                                    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], _params);
+                                    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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                    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, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            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;
+            }
+        }
+
+
+    }
+    public class schur
+    {
+        /*************************************************************************
+        Subroutine performing the Schur decomposition of a general matrix by using
+        the QR algorithm with multiple shifts.
+
+        COMMERCIAL EDITION OF ALGLIB:
+
+          ! Commercial version of ALGLIB includes one  important  improvement   of
+          ! this function, which can be used from C++ and C#:
+          ! * Intel MKL support (lightweight Intel MKL is shipped with ALGLIB)
+          !
+          ! Intel MKL gives approximately constant  (with  respect  to  number  of
+          ! worker threads) acceleration factor which depends on CPU  being  used,
+          ! problem  size  and  "baseline"  ALGLIB  edition  which  is  used   for
+          ! comparison.
+          !
+          ! Multithreaded acceleration is NOT supported for this function.
+          !
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            double[] tau = new double[0];
+            double[] wi = new double[0];
+            double[] wr = new double[0];
+            int info = 0;
+
+            s = new double[0,0];
+
+            
+            //
+            // Upper Hessenberg form of the 0-based matrix
+            //
+            ortfac.rmatrixhessenberg(ref a, n, ref tau, _params);
+            ortfac.rmatrixhessenbergunpackq(a, n, tau, ref s, _params);
+            
+            //
+            // Schur decomposition
+            //
+            hsschur.rmatrixinternalschurdecomposition(a, n, 1, 1, ref wr, ref wi, ref s, ref info, _params);
+            result = info==0;
+            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 hasn't 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 isn't 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            if( !result )
+            {
+                return result;
+            }
+            result = evd.smatrixevd(a, n, zneeded, isuppera, ref d, ref t, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params) )
+                {
+                    result = false;
+                    return result;
+                }
+                
+                //
+                // Invert L in T
+                //
+                matinv.rmatrixtrinverse(ref t, n, false, false, ref info, rep, _params);
+                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, _params);
+                    if( isuppera )
+                    {
+                        blas.matrixvectormultiply(a, 0, j-1, j, n-1, true, w1, 1, j, 1.0, ref w2, j+1, n, 0.0, _params);
+                    }
+                    else
+                    {
+                        blas.matrixvectormultiply(a, j, n-1, 0, j-1, false, w1, 1, j, 1.0, ref w2, j+1, n, 0.0, _params);
+                    }
+                    
+                    //
+                    // 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, _params) )
+                {
+                    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, _params);
+                    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, _params);
+                    }
+                    else
+                    {
+                        blas.matrixvectormultiply(a, j-1, n-1, 0, j-2, true, w1, j, n, 1.0, ref w2, 1, j-1, 0.0, _params);
+                    }
+                    
+                    //
+                    // 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, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params), "RMatrixDet: A contains infinite or NaN values!");
+            trfac.rmatrixlu(ref a, n, n, ref pivots, _params);
+            result = rmatrixludet(a, pivots, n, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params), "CMatrixDet: A contains infinite or NaN values!");
+            trfac.cmatrixlu(ref a, n, n, ref pivots, _params);
+            result = cmatrixludet(a, pivots, n, _params);
+            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,
+        it's 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,
+            alglib.xparams _params)
+        {
+            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 isn't used/changed  by
+                          function
+                        * if False, symmetric matrix  A  is  given  by  its lower
+                          triangle, and the upper triangle isn't 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,
+            alglib.xparams _params)
+        {
+            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, _params), "SPDMatrixDet: A contains infinite or NaN values!");
+            b = trfac.spdmatrixcholesky(ref a, n, isupper, _params);
+            alglib.ap.assert(b, "SPDMatrixDet: A is not SPD!");
+            result = spdmatrixcholeskydet(a, n, _params);
+            return result;
+        }
+
+
+    }
+}
+
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/optimization.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/optimization.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/optimization.cs	(revision 17158)
@@ -0,0 +1,60016 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 164
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This structure is used to store  OptGuard  report,  i.e.  report  on   the
+    properties of the nonlinear function being optimized with ALGLIB.
+
+    After you tell your optimizer to activate OptGuard  this technology starts
+    to silently monitor function values and gradients/Jacobians  being  passed
+    all around during your optimization session. Depending on specific set  of
+    checks enabled OptGuard may perform additional function evaluations  (say,
+    about 3*N evaluations if you want to check analytic gradient for errors).
+
+    Upon discovering that something strange happens  (function  values  and/or
+    gradient components change too sharply and/or unexpectedly) OptGuard  sets
+    one of the "suspicion  flags" (without interrupting optimization session).
+    After optimization is done, you can examine OptGuard report.
+
+    Following report fields can be set:
+    * nonc0suspected
+    * nonc1suspected
+    * badgradsuspected
+
+
+    === WHAT CAN BE DETECTED WITH OptGuard INTEGRITY CHECKER =================
+
+    Following  types  of  errors  in your target function (constraints) can be
+    caught:
+    a) discontinuous functions ("non-C0" part of the report)
+    b) functions with discontinuous derivative ("non-C1" part of the report)
+    c) errors in the analytic gradient provided by user
+
+    These types of errors result in optimizer  stopping  well  before reaching
+    solution (most often - right after encountering discontinuity).
+
+    Type A errors are usually  coding  errors  during  implementation  of  the
+    target function. Most "normal" problems involve continuous functions,  and
+    anyway you can't reliably optimize discontinuous function.
+
+    Type B errors are either coding errors or (in case code itself is correct)
+    evidence of the fact  that  your  problem  is  an  "incorrect"  one.  Most
+    optimizers (except for ones provided by MINNS subpackage) do  not  support
+    nonsmooth problems.
+
+    Type C errors are coding errors which often prevent optimizer from  making
+    even one step  or result in optimizing stopping  too  early,  as  soon  as
+    actual descent direction becomes too different from one suggested by user-
+    supplied gradient.
+
+
+    === WHAT IS REPORTED =====================================================
+
+    Following set of report fields deals with discontinuous  target functions,
+    ones not belonging to C0 continuity class:
+
+    * nonc0suspected - is a flag which is set upon discovering some indication
+      of the discontinuity. If this flag is false, the rest of "non-C0" fields
+      should be ignored
+    * nonc0fidx - is an index of the function (0 for  target  function,  1  or
+      higher for nonlinear constraints) which is suspected of being "non-C0"
+    * nonc0lipshitzc - a Lipchitz constant for a function which was  suspected
+      of being non-continuous.
+    * nonc0test0positive -  set  to  indicate  specific  test  which  detected
+      continuity violation (test #0)
+
+    Following set of report fields deals with discontinuous gradient/Jacobian,
+    i.e. with functions violating C1 continuity:
+
+    * nonc1suspected - is a flag which is set upon discovering some indication
+      of the discontinuity. If this flag is false, the rest of "non-C1" fields
+      should be ignored
+    * nonc1fidx - is an index of the function (0 for  target  function,  1  or
+      higher for nonlinear constraints) which is suspected of being "non-C1"
+    * nonc1lipshitzc - a Lipchitz constant for a function gradient  which  was
+      suspected of being non-smooth.
+    * nonc1test0positive -  set  to  indicate  specific  test  which  detected
+      continuity violation (test #0)
+    * nonc1test1positive -  set  to  indicate  specific  test  which  detected
+      continuity violation (test #1)
+
+    Following set of report fields deals with errors in the gradient:
+    * badgradsuspected - is a flad which is set upon discovering an  error  in
+      the analytic gradient supplied by user
+    * badgradfidx - index  of   the  function  with bad gradient (0 for target
+      function, 1 or higher for nonlinear constraints)
+    * badgradvidx - index of the variable
+    * badgradxbase - location where Jacobian is tested
+    * following  matrices  store  user-supplied  Jacobian  and  its  numerical
+      differentiation version (which is assumed to be  free  from  the  coding
+      errors), both of them computed near the initial point:
+      * badgraduser, an array[K,N], analytic Jacobian supplied by user
+      * badgradnum,  an array[K,N], numeric  Jacobian computed by ALGLIB
+      Here K is a total number of  nonlinear  functions  (target  +  nonlinear
+      constraints), N is a variable number.
+      The  element  of  badgraduser[] with index [badgradfidx,badgradvidx]  is
+      assumed to be wrong.
+
+    More detailed error log can  be  obtained  from  optimizer  by  explicitly
+    requesting reports for tests C0.0, C1.0, C1.1.
+
+      -- ALGLIB --
+         Copyright 19.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public class optguardreport : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public bool nonc0suspected { get { return _innerobj.nonc0suspected; } set { _innerobj.nonc0suspected = value; } }
+        public bool nonc0test0positive { get { return _innerobj.nonc0test0positive; } set { _innerobj.nonc0test0positive = value; } }
+        public int nonc0fidx { get { return _innerobj.nonc0fidx; } set { _innerobj.nonc0fidx = value; } }
+        public double nonc0lipschitzc { get { return _innerobj.nonc0lipschitzc; } set { _innerobj.nonc0lipschitzc = value; } }
+        public bool nonc1suspected { get { return _innerobj.nonc1suspected; } set { _innerobj.nonc1suspected = value; } }
+        public bool nonc1test0positive { get { return _innerobj.nonc1test0positive; } set { _innerobj.nonc1test0positive = value; } }
+        public bool nonc1test1positive { get { return _innerobj.nonc1test1positive; } set { _innerobj.nonc1test1positive = value; } }
+        public int nonc1fidx { get { return _innerobj.nonc1fidx; } set { _innerobj.nonc1fidx = value; } }
+        public double nonc1lipschitzc { get { return _innerobj.nonc1lipschitzc; } set { _innerobj.nonc1lipschitzc = value; } }
+        public bool badgradsuspected { get { return _innerobj.badgradsuspected; } set { _innerobj.badgradsuspected = value; } }
+        public int badgradfidx { get { return _innerobj.badgradfidx; } set { _innerobj.badgradfidx = value; } }
+        public int badgradvidx { get { return _innerobj.badgradvidx; } set { _innerobj.badgradvidx = value; } }
+        public double[] badgradxbase { get { return _innerobj.badgradxbase; } set { _innerobj.badgradxbase = value; } }
+        public double[,] badgraduser { get { return _innerobj.badgraduser; } set { _innerobj.badgraduser = value; } }
+        public double[,] badgradnum { get { return _innerobj.badgradnum; } set { _innerobj.badgradnum = value; } }
+    
+        public optguardreport()
+        {
+            _innerobj = new optguardapi.optguardreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new optguardreport((optguardapi.optguardreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private optguardapi.optguardreport _innerobj;
+        public optguardapi.optguardreport innerobj { get { return _innerobj; } }
+        public optguardreport(optguardapi.optguardreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This  structure  is  used  for  detailed   reporting  about  suspected  C1
+    continuity violation as flagged by C1 test #0 (OptGuard  has several tests
+    for C1 continuity, this report is used by #0).
+
+    === WHAT IS TESTED =======================================================
+
+    C1 test #0 studies function values (not gradient!)  obtained  during  line
+    searches and monitors behavior of directional  derivative  estimate.  This
+    test is less powerful than test #1, but it does  not  depend  on  gradient
+    values  and  thus  it  is  more  robust  against  artifacts  introduced by
+    numerical differentiation.
+
+
+    === WHAT IS REPORTED =====================================================
+
+    Actually, report retrieval function returns TWO report structures:
+
+    * one for most suspicious point found so far (one with highest  change  in
+      the directional derivative), so called "strongest" report
+    * another one for most detailed line search (more function  evaluations  =
+      easier to understand what's going on) which triggered  test #0 criteria,
+      so called "longest" report
+
+    In both cases following fields are returned:
+
+    * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+      did not notice anything (in the latter cases fields below are empty).
+    * fidx - is an index of the function (0 for  target  function, 1 or higher
+      for nonlinear constraints) which is suspected of being "non-C1"
+    * x0[], d[] - arrays of length N which store initial point  and  direction
+      for line search (d[] can be normalized, but does not have to)
+    * stp[], f[] - arrays of length CNT which store step lengths and  function
+      values at these points; f[i] is evaluated in x0+stp[i]*d.
+    * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+      between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+      with  most  likely  position  of  the  violation  between  stpidxa+1 and
+      stpidxa+2.
+
+    You can plot function values stored in stp[]  and  f[]  arrays  and  study
+    behavior of your function by your own eyes, just  to  be  sure  that  test
+    correctly reported C1 violation.
+
+      -- ALGLIB --
+         Copyright 19.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public class optguardnonc1test0report : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public bool positive { get { return _innerobj.positive; } set { _innerobj.positive = value; } }
+        public int fidx { get { return _innerobj.fidx; } set { _innerobj.fidx = value; } }
+        public double[] x0 { get { return _innerobj.x0; } set { _innerobj.x0 = value; } }
+        public double[] d { get { return _innerobj.d; } set { _innerobj.d = value; } }
+        public int n { get { return _innerobj.n; } set { _innerobj.n = value; } }
+        public double[] stp { get { return _innerobj.stp; } set { _innerobj.stp = value; } }
+        public double[] f { get { return _innerobj.f; } set { _innerobj.f = value; } }
+        public int cnt { get { return _innerobj.cnt; } set { _innerobj.cnt = value; } }
+        public int stpidxa { get { return _innerobj.stpidxa; } set { _innerobj.stpidxa = value; } }
+        public int stpidxb { get { return _innerobj.stpidxb; } set { _innerobj.stpidxb = value; } }
+    
+        public optguardnonc1test0report()
+        {
+            _innerobj = new optguardapi.optguardnonc1test0report();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new optguardnonc1test0report((optguardapi.optguardnonc1test0report)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private optguardapi.optguardnonc1test0report _innerobj;
+        public optguardapi.optguardnonc1test0report innerobj { get { return _innerobj; } }
+        public optguardnonc1test0report(optguardapi.optguardnonc1test0report obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This  structure  is  used  for  detailed   reporting  about  suspected  C1
+    continuity violation as flagged by C1 test #1 (OptGuard  has several tests
+    for C1 continuity, this report is used by #1).
+
+    === WHAT IS TESTED =======================================================
+
+    C1 test #1 studies individual  components  of  the  gradient  as  recorded
+    during line searches. Upon discovering discontinuity in the gradient  this
+    test records specific component which was suspected (or  one  with  highest
+    indication of discontinuity if multiple components are suspected).
+
+    When precise analytic gradient is provided this test is more powerful than
+    test #0  which  works  with  function  values  and  ignores  user-provided
+    gradient.  However,  test  #0  becomes  more   powerful   when   numerical
+    differentiation is employed (in such cases test #1 detects  higher  levels
+    of numerical noise and becomes too conservative).
+
+    This test also tells specific components of the gradient which violate  C1
+    continuity, which makes it more informative than #0, which just tells that
+    continuity is violated.
+
+
+    === WHAT IS REPORTED =====================================================
+
+    Actually, report retrieval function returns TWO report structures:
+
+    * one for most suspicious point found so far (one with highest  change  in
+      the directional derivative), so called "strongest" report
+    * another one for most detailed line search (more function  evaluations  =
+      easier to understand what's going on) which triggered  test #1 criteria,
+      so called "longest" report
+
+    In both cases following fields are returned:
+
+    * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+      did not notice anything (in the latter cases fields below are empty).
+    * fidx - is an index of the function (0 for  target  function, 1 or higher
+      for nonlinear constraints) which is suspected of being "non-C1"
+    * vidx - is an index of the variable in [0,N) with nonsmooth derivative
+    * x0[], d[] - arrays of length N which store initial point  and  direction
+      for line search (d[] can be normalized, but does not have to)
+    * stp[], g[] - arrays of length CNT which store step lengths and  gradient
+      values at these points; g[i] is evaluated in  x0+stp[i]*d  and  contains
+      vidx-th component of the gradient.
+    * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+      between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+      with  most  likely  position  of  the  violation  between  stpidxa+1 and
+      stpidxa+2.
+
+    You can plot function values stored in stp[]  and  g[]  arrays  and  study
+    behavior of your function by your own eyes, just  to  be  sure  that  test
+    correctly reported C1 violation.
+
+      -- ALGLIB --
+         Copyright 19.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public class optguardnonc1test1report : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public bool positive { get { return _innerobj.positive; } set { _innerobj.positive = value; } }
+        public int fidx { get { return _innerobj.fidx; } set { _innerobj.fidx = value; } }
+        public int vidx { get { return _innerobj.vidx; } set { _innerobj.vidx = value; } }
+        public double[] x0 { get { return _innerobj.x0; } set { _innerobj.x0 = value; } }
+        public double[] d { get { return _innerobj.d; } set { _innerobj.d = value; } }
+        public int n { get { return _innerobj.n; } set { _innerobj.n = value; } }
+        public double[] stp { get { return _innerobj.stp; } set { _innerobj.stp = value; } }
+        public double[] g { get { return _innerobj.g; } set { _innerobj.g = value; } }
+        public int cnt { get { return _innerobj.cnt; } set { _innerobj.cnt = value; } }
+        public int stpidxa { get { return _innerobj.stpidxa; } set { _innerobj.stpidxa = value; } }
+        public int stpidxb { get { return _innerobj.stpidxb; } set { _innerobj.stpidxb = value; } }
+    
+        public optguardnonc1test1report()
+        {
+            _innerobj = new optguardapi.optguardnonc1test1report();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new optguardnonc1test1report((optguardapi.optguardnonc1test1report)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private optguardapi.optguardnonc1test1report _innerobj;
+        public optguardapi.optguardnonc1test1report innerobj { get { return _innerobj; } }
+        public optguardnonc1test1report(optguardapi.optguardnonc1test1report obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class minlbfgsstate : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minlbfgsstate((minlbfgs.minlbfgsstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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;
+        }
+    }
+
+
+    /*************************************************************************
+    This structure stores optimization report:
+    * IterationsCount           total number of inner iterations
+    * NFEV                      number of gradient evaluations
+    * TerminationType           termination type (see below)
+
+    TERMINATION CODES
+
+    TerminationType field contains completion code, which can be:
+      -8    internal integrity control detected  infinite  or  NAN  values  in
+            function/gradient. Abnormal termination signalled.
+       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,
+            X contains best point found so far.
+       8    terminated    by  user  who  called  minlbfgsrequesttermination().
+            X contains point which was   "current accepted"  when  termination
+            request was submitted.
+
+    Other fields of this structure are not documented and should not be used!
+    *************************************************************************/
+    public class minlbfgsreport : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minlbfgsreport((minlbfgs.minlbfgsreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void minlbfgscreate(int n, int m, double[] x, out minlbfgsstate state, alglib.xparams _params)
+    {
+        state = new minlbfgsstate();
+        minlbfgs.minlbfgscreate(n, m, x, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void minlbfgscreate(int m, double[] x, out minlbfgsstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minlbfgsstate();
+        n = ap.len(x);
+        minlbfgs.minlbfgscreate(n, m, x, state.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void minlbfgscreatef(int n, int m, double[] x, double diffstep, out minlbfgsstate state, alglib.xparams _params)
+    {
+        state = new minlbfgsstate();
+        minlbfgs.minlbfgscreatef(n, m, x, diffstep, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void minlbfgscreatef(int m, double[] x, double diffstep, out minlbfgsstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minlbfgsstate();
+        n = ap.len(x);
+        minlbfgs.minlbfgscreatef(n, m, x, diffstep, state.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void minlbfgssetcond(minlbfgsstate state, double epsg, double epsf, double epsx, int maxits, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgssetcond(state.innerobj, epsg, epsf, epsx, maxits, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlbfgssetxrep(minlbfgsstate state, bool needxrep, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgssetxrep(state.innerobj, needxrep, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlbfgssetstpmax(minlbfgsstate state, double stpmax, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgssetstpmax(state.innerobj, stpmax, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlbfgssetscale(minlbfgsstate state, double[] s, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgssetscale(state.innerobj, s, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlbfgssetprecdefault(minlbfgsstate state, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgssetprecdefault(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlbfgssetpreccholesky(minlbfgsstate state, double[,] p, bool isupper, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgssetpreccholesky(state.innerobj, p, isupper, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlbfgssetprecdiag(minlbfgsstate state, double[] d, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgssetprecdiag(state.innerobj, d, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlbfgssetprecscale(minlbfgsstate state, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgssetprecscale(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return minlbfgs.minlbfgsiteration(state.innerobj, null);
+    }
+    
+    public static bool minlbfgsiteration(minlbfgsstate state, alglib.xparams _params)
+    {
+    
+        return minlbfgs.minlbfgsiteration(state.innerobj, _params);
+    }
+    /*************************************************************************
+    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)
+    {
+        minlbfgsoptimize(state, func, rep, obj, null);
+    }
+    
+    public static void minlbfgsoptimize(minlbfgsstate state, ndimensional_func func, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'minlbfgsoptimize()' (func is null)");
+        while( alglib.minlbfgsiteration(state, _params) )
+        {
+            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)
+    {
+        minlbfgsoptimize(state, grad, rep, obj, null);
+    }
+    
+    public static void minlbfgsoptimize(minlbfgsstate state, ndimensional_grad grad, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'minlbfgsoptimize()' (grad is null)");
+        while( alglib.minlbfgsiteration(state, _params) )
+        {
+            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?)");
+        }
+    }
+
+
+    
+    /*************************************************************************
+    This  function  activates/deactivates verification  of  the  user-supplied
+    analytic gradient.
+
+    Upon  activation  of  this  option  OptGuard  integrity  checker  performs
+    numerical differentiation of your target function  at  the  initial  point
+    (note: future versions may also perform check  at  the  final  point)  and
+    compares numerical gradient with analytic one provided by you.
+
+    If difference is too large, an error flag is set and optimization  session
+    continues. After optimization session is over, you can retrieve the report
+    which  stores  both  gradients  and  specific  components  highlighted  as
+    suspicious by the OptGuard.
+
+    The primary OptGuard report can be retrieved with minlbfgsoptguardresults().
+
+    IMPORTANT: gradient check is a high-overhead option which  will  cost  you
+               about 3*N additional function evaluations. In many cases it may
+               cost as much as the rest of the optimization session.
+
+               YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO
+               CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY.
+
+    NOTE: unlike previous incarnation of the gradient checking code,  OptGuard
+          does NOT interrupt optimization even if it discovers bad gradient.
+
+    INPUT PARAMETERS:
+        State       -   structure used to store algorithm state
+        TestStep    -   verification step used for numerical differentiation:
+                        * TestStep=0 turns verification off
+                        * TestStep>0 activates verification
+                        You should carefully choose TestStep. Value  which  is
+                        too large (so large that  function  behavior  is  non-
+                        cubic at this scale) will lead  to  false  alarms. Too
+                        short step will result in rounding  errors  dominating
+                        numerical derivative.
+
+                        You may use different step for different parameters by
+                        means of setting scale with minlbfgssetscale().
+
+    === EXPLANATION ==========================================================
+
+    In order to verify gradient algorithm performs following steps:
+      * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i],
+        where X[i] is i-th component of the initial point and S[i] is a  scale
+        of i-th parameter
+      * F(X) is evaluated at these trial points
+      * we perform one more evaluation in the middle point of the interval
+      * we  build  cubic  model using function values and derivatives at trial
+        points and we compare its prediction with actual value in  the  middle
+        point
+
+      -- ALGLIB --
+         Copyright 15.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgsoptguardgradient(minlbfgsstate state, double teststep)
+    {
+    
+        minlbfgs.minlbfgsoptguardgradient(state.innerobj, teststep, null);
+    }
+    
+    public static void minlbfgsoptguardgradient(minlbfgsstate state, double teststep, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgsoptguardgradient(state.innerobj, teststep, _params);
+    }
+    
+    /*************************************************************************
+    This  function  activates/deactivates nonsmoothness monitoring  option  of
+    the  OptGuard  integrity  checker. Smoothness  monitor  silently  observes
+    solution process and tries to detect ill-posed problems, i.e. ones with:
+    a) discontinuous target function (non-C0)
+    b) nonsmooth     target function (non-C1)
+
+    Smoothness monitoring does NOT interrupt optimization  even if it suspects
+    that your problem is nonsmooth. It just sets corresponding  flags  in  the
+    OptGuard report which can be retrieved after optimization is over.
+
+    Smoothness monitoring is a moderate overhead option which often adds  less
+    than 1% to the optimizer running time. Thus, you can use it even for large
+    scale problems.
+
+    NOTE: OptGuard does  NOT  guarantee  that  it  will  always  detect  C0/C1
+          continuity violations.
+
+          First, minor errors are hard to  catch - say, a 0.0001 difference in
+          the model values at two sides of the gap may be due to discontinuity
+          of the model - or simply because the model has changed.
+
+          Second, C1-violations  are  especially  difficult  to  detect  in  a
+          noninvasive way. The optimizer usually  performs  very  short  steps
+          near the nonsmoothness, and differentiation  usually   introduces  a
+          lot of numerical noise.  It  is  hard  to  tell  whether  some  tiny
+          discontinuity in the slope is due to real nonsmoothness or just  due
+          to numerical noise alone.
+
+          Our top priority was to avoid false positives, so in some rare cases
+          minor errors may went unnoticed (however, in most cases they can  be
+          spotted with restart from different initial point).
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+        level   -   monitoring level:
+                    * 0 - monitoring is disabled
+                    * 1 - noninvasive low-overhead monitoring; function values
+                          and/or gradients are recorded, but OptGuard does not
+                          try to perform additional evaluations  in  order  to
+                          get more information about suspicious locations.
+
+    === EXPLANATION ==========================================================
+
+    One major source of headache during optimization  is  the  possibility  of
+    the coding errors in the target function/constraints (or their gradients).
+    Such  errors   most   often   manifest   themselves  as  discontinuity  or
+    nonsmoothness of the target/constraints.
+
+    Another frequent situation is when you try to optimize something involving
+    lots of min() and max() operations, i.e. nonsmooth target. Although not  a
+    coding error, it is nonsmoothness anyway - and smooth  optimizers  usually
+    stop right after encountering nonsmoothness, well before reaching solution.
+
+    OptGuard integrity checker helps you to catch such situations: it monitors
+    function values/gradients being passed  to  the  optimizer  and  tries  to
+    errors. Upon discovering suspicious pair of points it  raises  appropriate
+    flag (and allows you to continue optimization). When optimization is done,
+    you can study OptGuard result.
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgsoptguardsmoothness(minlbfgsstate state, int level)
+    {
+    
+        minlbfgs.minlbfgsoptguardsmoothness(state.innerobj, level, null);
+    }
+    
+    public static void minlbfgsoptguardsmoothness(minlbfgsstate state, int level, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgsoptguardsmoothness(state.innerobj, level, _params);
+    }
+            
+    public static void minlbfgsoptguardsmoothness(minlbfgsstate state)
+    {
+        int level;
+    
+    
+        level = 1;
+        minlbfgs.minlbfgsoptguardsmoothness(state.innerobj, level, null);
+    
+        return;
+    }
+            
+    public static void minlbfgsoptguardsmoothness(minlbfgsstate state, alglib.xparams _params)
+    {
+        int level;
+    
+    
+        level = 1;
+        minlbfgs.minlbfgsoptguardsmoothness(state.innerobj, level, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Results of OptGuard integrity check, should be called  after  optimization
+    session is over.
+
+    === PRIMARY REPORT =======================================================
+
+    OptGuard performs several checks which are intended to catch common errors
+    in the implementation of nonlinear function/gradient:
+    * incorrect analytic gradient
+    * discontinuous (non-C0) target functions (constraints)
+    * nonsmooth     (non-C1) target functions (constraints)
+
+    Each of these checks is activated with appropriate function:
+    * minlbfgsoptguardgradient() for gradient verification
+    * minlbfgsoptguardsmoothness() for C0/C1 checks
+
+    Following flags are set when these errors are suspected:
+    * rep.badgradsuspected, and additionally:
+      * rep.badgradvidx for specific variable (gradient element) suspected
+      * rep.badgradxbase, a point where gradient is tested
+      * rep.badgraduser, user-provided gradient  (stored  as  2D  matrix  with
+        single row in order to make  report  structure  compatible  with  more
+        complex optimizers like MinNLC or MinLM)
+      * rep.badgradnum,   reference    gradient    obtained    via   numerical
+        differentiation (stored as  2D matrix with single row in order to make
+        report structure compatible with more complex optimizers  like  MinNLC
+        or MinLM)
+    * rep.nonc0suspected
+    * rep.nonc1suspected
+
+    === ADDITIONAL REPORTS/LOGS ==============================================
+
+    Several different tests are performed to catch C0/C1 errors, you can  find
+    out specific test signaled error by looking to:
+    * rep.nonc0test0positive, for non-C0 test #0
+    * rep.nonc1test0positive, for non-C1 test #0
+    * rep.nonc1test1positive, for non-C1 test #1
+
+    Additional information (including line search logs)  can  be  obtained  by
+    means of:
+    * minlbfgsoptguardnonc1test0results()
+    * minlbfgsoptguardnonc1test1results()
+    which return detailed error reports, specific points where discontinuities
+    were found, and so on.
+
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        rep     -   generic OptGuard report;  more  detailed  reports  can  be
+                    retrieved with other functions.
+
+    NOTE: false negatives (nonsmooth problems are not identified as  nonsmooth
+          ones) are possible although unlikely.
+
+          The reason  is  that  you  need  to  make several evaluations around
+          nonsmoothness  in  order  to  accumulate  enough  information  about
+          function curvature. Say, if you start right from the nonsmooth point,
+          optimizer simply won't get enough data to understand what  is  going
+          wrong before it terminates due to abrupt changes in the  derivative.
+          It is also  possible  that  "unlucky"  step  will  move  us  to  the
+          termination too quickly.
+
+          Our current approach is to have less than 0.1%  false  negatives  in
+          our test examples  (measured  with  multiple  restarts  from  random
+          points), and to have exactly 0% false positives.
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgsoptguardresults(minlbfgsstate state, out optguardreport rep)
+    {
+        rep = new optguardreport();
+        minlbfgs.minlbfgsoptguardresults(state.innerobj, rep.innerobj, null);
+    }
+    
+    public static void minlbfgsoptguardresults(minlbfgsstate state, out optguardreport rep, alglib.xparams _params)
+    {
+        rep = new optguardreport();
+        minlbfgs.minlbfgsoptguardresults(state.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Detailed results of the OptGuard integrity check for nonsmoothness test #0
+
+    Nonsmoothness (non-C1) test #0 studies  function  values  (not  gradient!)
+    obtained during line searches and monitors  behavior  of  the  directional
+    derivative estimate.
+
+    This test is less powerful than test #1, but it does  not  depend  on  the
+    gradient values and thus it is more robust against artifacts introduced by
+    numerical differentiation.
+
+    Two reports are returned:
+    * a "strongest" one, corresponding  to  line   search  which  had  highest
+      value of the nonsmoothness indicator
+    * a "longest" one, corresponding to line search which  had  more  function
+      evaluations, and thus is more detailed
+
+    In both cases following fields are returned:
+
+    * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+      did not notice anything (in the latter cases fields below are empty).
+    * x0[], d[] - arrays of length N which store initial point  and  direction
+      for line search (d[] can be normalized, but does not have to)
+    * stp[], f[] - arrays of length CNT which store step lengths and  function
+      values at these points; f[i] is evaluated in x0+stp[i]*d.
+    * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+      between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+      with  most  likely  position  of  the  violation  between  stpidxa+1 and
+      stpidxa+2.
+
+    ==========================================================================
+    = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+    =                   see where C1 continuity is violated.
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        strrep  -   C1 test #0 "strong" report
+        lngrep  -   C1 test #0 "long" report
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgsoptguardnonc1test0results(minlbfgsstate state, out optguardnonc1test0report strrep, out optguardnonc1test0report lngrep)
+    {
+        strrep = new optguardnonc1test0report();
+        lngrep = new optguardnonc1test0report();
+        minlbfgs.minlbfgsoptguardnonc1test0results(state.innerobj, strrep.innerobj, lngrep.innerobj, null);
+    }
+    
+    public static void minlbfgsoptguardnonc1test0results(minlbfgsstate state, out optguardnonc1test0report strrep, out optguardnonc1test0report lngrep, alglib.xparams _params)
+    {
+        strrep = new optguardnonc1test0report();
+        lngrep = new optguardnonc1test0report();
+        minlbfgs.minlbfgsoptguardnonc1test0results(state.innerobj, strrep.innerobj, lngrep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Detailed results of the OptGuard integrity check for nonsmoothness test #1
+
+    Nonsmoothness (non-C1)  test  #1  studies  individual  components  of  the
+    gradient computed during line search.
+
+    When precise analytic gradient is provided this test is more powerful than
+    test #0  which  works  with  function  values  and  ignores  user-provided
+    gradient.  However,  test  #0  becomes  more   powerful   when   numerical
+    differentiation is employed (in such cases test #1 detects  higher  levels
+    of numerical noise and becomes too conservative).
+
+    This test also tells specific components of the gradient which violate  C1
+    continuity, which makes it more informative than #0, which just tells that
+    continuity is violated.
+
+    Two reports are returned:
+    * a "strongest" one, corresponding  to  line   search  which  had  highest
+      value of the nonsmoothness indicator
+    * a "longest" one, corresponding to line search which  had  more  function
+      evaluations, and thus is more detailed
+
+    In both cases following fields are returned:
+
+    * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+      did not notice anything (in the latter cases fields below are empty).
+    * vidx - is an index of the variable in [0,N) with nonsmooth derivative
+    * x0[], d[] - arrays of length N which store initial point  and  direction
+      for line search (d[] can be normalized, but does not have to)
+    * stp[], g[] - arrays of length CNT which store step lengths and  gradient
+      values at these points; g[i] is evaluated in  x0+stp[i]*d  and  contains
+      vidx-th component of the gradient.
+    * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+      between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+      with  most  likely  position  of  the  violation  between  stpidxa+1 and
+      stpidxa+2.
+
+    ==========================================================================
+    = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+    =                   see where C1 continuity is violated.
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        strrep  -   C1 test #1 "strong" report
+        lngrep  -   C1 test #1 "long" report
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgsoptguardnonc1test1results(minlbfgsstate state, out optguardnonc1test1report strrep, out optguardnonc1test1report lngrep)
+    {
+        strrep = new optguardnonc1test1report();
+        lngrep = new optguardnonc1test1report();
+        minlbfgs.minlbfgsoptguardnonc1test1results(state.innerobj, strrep.innerobj, lngrep.innerobj, null);
+    }
+    
+    public static void minlbfgsoptguardnonc1test1results(minlbfgsstate state, out optguardnonc1test1report strrep, out optguardnonc1test1report lngrep, alglib.xparams _params)
+    {
+        strrep = new optguardnonc1test1report();
+        lngrep = new optguardnonc1test1report();
+        minlbfgs.minlbfgsoptguardnonc1test1results(state.innerobj, strrep.innerobj, lngrep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    L-BFGS algorithm results
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[0..N-1], solution
+        Rep     -   optimization report:
+                    * Rep.TerminationType completetion code:
+                        * -8    internal integrity control  detected  infinite
+                                or NAN values in  function/gradient.  Abnormal
+                                termination signalled.
+                        * -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
+                        *  8    terminated by user who called minlbfgsrequesttermination().
+                                X contains point which was "current accepted" when
+                                termination request was submitted.
+                    * 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, null);
+    }
+    
+    public static void minlbfgsresults(minlbfgsstate state, out double[] x, out minlbfgsreport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new minlbfgsreport();
+        minlbfgs.minlbfgsresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlbfgsresultsbuf(minlbfgsstate state, ref double[] x, minlbfgsreport rep, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgsresultsbuf(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlbfgsrestartfrom(minlbfgsstate state, double[] x, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgsrestartfrom(state.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine submits request for termination of running  optimizer.  It
+    should be called from user-supplied callback when user decides that it  is
+    time to "smoothly" terminate optimization process.  As  result,  optimizer
+    stops at point which was "current accepted" when termination  request  was
+    submitted and returns error code 8 (successful termination).
+
+    INPUT PARAMETERS:
+        State   -   optimizer structure
+
+    NOTE: after  request  for  termination  optimizer  may   perform   several
+          additional calls to user-supplied callbacks. It does  NOT  guarantee
+          to stop immediately - it just guarantees that these additional calls
+          will be discarded later.
+
+    NOTE: calling this function on optimizer which is NOT running will have no
+          effect.
+
+    NOTE: multiple calls to this function are possible. First call is counted,
+          subsequent calls are silently ignored.
+
+      -- ALGLIB --
+         Copyright 08.10.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlbfgsrequesttermination(minlbfgsstate state)
+    {
+    
+        minlbfgs.minlbfgsrequesttermination(state.innerobj, null);
+    }
+    
+    public static void minlbfgsrequesttermination(minlbfgsstate state, alglib.xparams _params)
+    {
+    
+        minlbfgs.minlbfgsrequesttermination(state.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+
+}
+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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minbleicstate((minbleic.minbleicstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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:
+    * IterationsCount           number of iterations
+    * NFEV                      number of gradient evaluations
+    * TerminationType           termination type (see below)
+
+    TERMINATION CODES
+
+    TerminationType field contains completion code, which can be:
+      -8    internal integrity control detected  infinite  or  NAN  values  in
+            function/gradient. Abnormal termination signalled.
+      -3    inconsistent constraints. Feasible point is
+            either nonexistent or too hard to find. Try to
+            restart optimizer with better initial approximation
+       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,
+            X contains best point found so far.
+       8    terminated by user who called minbleicrequesttermination(). X contains
+            point which was "current accepted" when  termination  request  was
+            submitted.
+
+    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 : alglibobject
+    {
+        //
+        // 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 varidx { get { return _innerobj.varidx; } set { _innerobj.varidx = 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 int inneriterationscount { get { return _innerobj.inneriterationscount; } set { _innerobj.inneriterationscount = value; } }
+        public int outeriterationscount { get { return _innerobj.outeriterationscount; } set { _innerobj.outeriterationscount = value; } }
+    
+        public minbleicreport()
+        {
+            _innerobj = new minbleic.minbleicreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minbleicreport((minbleic.minbleicreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 with MinBLEICSetCond().
+
+    4. User calls MinBLEICOptimize() function which takes algorithm  state and
+       pointer (delegate, etc.) to callback function which calculates F/G.
+
+    5. User calls MinBLEICResults() to get solution
+
+    6. Optionally user may call MinBLEICRestartFrom() to solve another problem
+       with same N but another starting point.
+       MinBLEICRestartFrom() allows to reuse already initialized structure.
+
+    NOTE: if you have box-only constraints (no  general  linear  constraints),
+          then MinBC optimizer can be better option. It uses  special,  faster
+          constraint activation method, which performs better on problems with
+          multiple constraints active at the solution.
+
+          On small-scale problems performance of MinBC is similar to  that  of
+          MinBLEIC, but on large-scale ones (hundreds and thousands of  active
+          constraints) it can be several times faster than MinBLEIC.
+
+    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, null);
+    }
+    
+    public static void minbleiccreate(int n, double[] x, out minbleicstate state, alglib.xparams _params)
+    {
+        state = new minbleicstate();
+        minbleic.minbleiccreate(n, x, state.innerobj, _params);
+    }
+            
+    public static void minbleiccreate(double[] x, out minbleicstate state)
+    {
+        int n;
+    
+        state = new minbleicstate();
+        n = ap.len(x);
+        minbleic.minbleiccreate(n, x, state.innerobj, null);
+    
+        return;
+    }
+            
+    public static void minbleiccreate(double[] x, out minbleicstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minbleicstate();
+        n = ap.len(x);
+        minbleic.minbleiccreate(n, x, state.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void minbleiccreatef(int n, double[] x, double diffstep, out minbleicstate state, alglib.xparams _params)
+    {
+        state = new minbleicstate();
+        minbleic.minbleiccreatef(n, x, diffstep, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void minbleiccreatef(double[] x, double diffstep, out minbleicstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minbleicstate();
+        n = ap.len(x);
+        minbleic.minbleiccreatef(n, x, diffstep, state.innerobj, _params);
+    
+        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().
+
+    NOTE: if you have box-only constraints (no  general  linear  constraints),
+          then MinBC optimizer can be better option. It uses  special,  faster
+          constraint activation method, which performs better on problems with
+          multiple constraints active at the solution.
+
+          On small-scale problems performance of MinBC is similar to  that  of
+          MinBLEIC, but on large-scale ones (hundreds and thousands of  active
+          constraints) it can be several times faster than MinBLEIC.
+
+    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, null);
+    }
+    
+    public static void minbleicsetbc(minbleicstate state, double[] bndl, double[] bndu, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicsetbc(state.innerobj, bndl, bndu, _params);
+    }
+    
+    /*************************************************************************
+    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]
+                    * if CT[i]=0, then I-th constraint is C[i,*]*x  = C[i,n]
+                    * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n]
+        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, null);
+    }
+    
+    public static void minbleicsetlc(minbleicstate state, double[,] c, int[] ct, int k, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicsetlc(state.innerobj, c, ct, k, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void minbleicsetlc(minbleicstate state, double[,] c, int[] ct, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function sets stopping conditions for the optimizer.
+
+    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 - step vector, dx=X(k+1)-X(k)
+                    * s - scaling coefficients set by MinBLEICSetScale()
+        MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                    iterations is unlimited.
+
+    Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead
+    to automatic stopping criterion selection.
+
+    NOTE: when SetCond() called with non-zero MaxIts, BLEIC solver may perform
+          slightly more than MaxIts iterations. I.e., MaxIts  sets  non-strict
+          limit on iterations count.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicsetcond(minbleicstate state, double epsg, double epsf, double epsx, int maxits)
+    {
+    
+        minbleic.minbleicsetcond(state.innerobj, epsg, epsf, epsx, maxits, null);
+    }
+    
+    public static void minbleicsetcond(minbleicstate state, double epsg, double epsf, double epsx, int maxits, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicsetcond(state.innerobj, epsg, epsf, epsx, maxits, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minbleicsetscale(minbleicstate state, double[] s, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicsetscale(state.innerobj, s, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minbleicsetprecdefault(minbleicstate state, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicsetprecdefault(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minbleicsetprecdiag(minbleicstate state, double[] d, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicsetprecdiag(state.innerobj, d, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minbleicsetprecscale(minbleicstate state, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicsetprecscale(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minbleicsetxrep(minbleicstate state, bool needxrep, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicsetxrep(state.innerobj, needxrep, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minbleicsetstpmax(minbleicstate state, double stpmax, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicsetstpmax(state.innerobj, stpmax, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return minbleic.minbleiciteration(state.innerobj, null);
+    }
+    
+    public static bool minbleiciteration(minbleicstate state, alglib.xparams _params)
+    {
+    
+        return minbleic.minbleiciteration(state.innerobj, _params);
+    }
+    /*************************************************************************
+    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 MinBLEICOptimize()) 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)
+    {
+        minbleicoptimize(state, func, rep, obj, null);
+    }
+    
+    public static void minbleicoptimize(minbleicstate state, ndimensional_func func, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'minbleicoptimize()' (func is null)");
+        while( alglib.minbleiciteration(state, _params) )
+        {
+            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)
+    {
+        minbleicoptimize(state, grad, rep, obj, null);
+    }
+    
+    public static void minbleicoptimize(minbleicstate state, ndimensional_grad grad, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'minbleicoptimize()' (grad is null)");
+        while( alglib.minbleiciteration(state, _params) )
+        {
+            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?)");
+        }
+    }
+
+
+    
+    /*************************************************************************
+    This  function  activates/deactivates verification  of  the  user-supplied
+    analytic gradient.
+
+    Upon  activation  of  this  option  OptGuard  integrity  checker  performs
+    numerical differentiation of your target function  at  the  initial  point
+    (note: future versions may also perform check  at  the  final  point)  and
+    compares numerical gradient with analytic one provided by you.
+
+    If difference is too large, an error flag is set and optimization  session
+    continues. After optimization session is over, you can retrieve the report
+    which  stores  both  gradients  and  specific  components  highlighted  as
+    suspicious by the OptGuard.
+
+    The primary OptGuard report can be retrieved with minbleicoptguardresults().
+
+    IMPORTANT: gradient check is a high-overhead option which  will  cost  you
+               about 3*N additional function evaluations. In many cases it may
+               cost as much as the rest of the optimization session.
+
+               YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO
+               CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY.
+
+    NOTE: unlike previous incarnation of the gradient checking code,  OptGuard
+          does NOT interrupt optimization even if it discovers bad gradient.
+
+    INPUT PARAMETERS:
+        State       -   structure used to store algorithm state
+        TestStep    -   verification step used for numerical differentiation:
+                        * TestStep=0 turns verification off
+                        * TestStep>0 activates verification
+                        You should carefully choose TestStep. Value  which  is
+                        too large (so large that  function  behavior  is  non-
+                        cubic at this scale) will lead  to  false  alarms. Too
+                        short step will result in rounding  errors  dominating
+                        numerical derivative.
+
+                        You may use different step for different parameters by
+                        means of setting scale with minbleicsetscale().
+
+    === EXPLANATION ==========================================================
+
+    In order to verify gradient algorithm performs following steps:
+      * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i],
+        where X[i] is i-th component of the initial point and S[i] is a  scale
+        of i-th parameter
+      * F(X) is evaluated at these trial points
+      * we perform one more evaluation in the middle point of the interval
+      * we  build  cubic  model using function values and derivatives at trial
+        points and we compare its prediction with actual value in  the  middle
+        point
+
+      -- ALGLIB --
+         Copyright 15.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicoptguardgradient(minbleicstate state, double teststep)
+    {
+    
+        minbleic.minbleicoptguardgradient(state.innerobj, teststep, null);
+    }
+    
+    public static void minbleicoptguardgradient(minbleicstate state, double teststep, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicoptguardgradient(state.innerobj, teststep, _params);
+    }
+    
+    /*************************************************************************
+    This  function  activates/deactivates nonsmoothness monitoring  option  of
+    the  OptGuard  integrity  checker. Smoothness  monitor  silently  observes
+    solution process and tries to detect ill-posed problems, i.e. ones with:
+    a) discontinuous target function (non-C0)
+    b) nonsmooth     target function (non-C1)
+
+    Smoothness monitoring does NOT interrupt optimization  even if it suspects
+    that your problem is nonsmooth. It just sets corresponding  flags  in  the
+    OptGuard report which can be retrieved after optimization is over.
+
+    Smoothness monitoring is a moderate overhead option which often adds  less
+    than 1% to the optimizer running time. Thus, you can use it even for large
+    scale problems.
+
+    NOTE: OptGuard does  NOT  guarantee  that  it  will  always  detect  C0/C1
+          continuity violations.
+
+          First, minor errors are hard to  catch - say, a 0.0001 difference in
+          the model values at two sides of the gap may be due to discontinuity
+          of the model - or simply because the model has changed.
+
+          Second, C1-violations  are  especially  difficult  to  detect  in  a
+          noninvasive way. The optimizer usually  performs  very  short  steps
+          near the nonsmoothness, and differentiation  usually   introduces  a
+          lot of numerical noise.  It  is  hard  to  tell  whether  some  tiny
+          discontinuity in the slope is due to real nonsmoothness or just  due
+          to numerical noise alone.
+
+          Our top priority was to avoid false positives, so in some rare cases
+          minor errors may went unnoticed (however, in most cases they can  be
+          spotted with restart from different initial point).
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+        level   -   monitoring level:
+                    * 0 - monitoring is disabled
+                    * 1 - noninvasive low-overhead monitoring; function values
+                          and/or gradients are recorded, but OptGuard does not
+                          try to perform additional evaluations  in  order  to
+                          get more information about suspicious locations.
+
+    === EXPLANATION ==========================================================
+
+    One major source of headache during optimization  is  the  possibility  of
+    the coding errors in the target function/constraints (or their gradients).
+    Such  errors   most   often   manifest   themselves  as  discontinuity  or
+    nonsmoothness of the target/constraints.
+
+    Another frequent situation is when you try to optimize something involving
+    lots of min() and max() operations, i.e. nonsmooth target. Although not  a
+    coding error, it is nonsmoothness anyway - and smooth  optimizers  usually
+    stop right after encountering nonsmoothness, well before reaching solution.
+
+    OptGuard integrity checker helps you to catch such situations: it monitors
+    function values/gradients being passed  to  the  optimizer  and  tries  to
+    errors. Upon discovering suspicious pair of points it  raises  appropriate
+    flag (and allows you to continue optimization). When optimization is done,
+    you can study OptGuard result.
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicoptguardsmoothness(minbleicstate state, int level)
+    {
+    
+        minbleic.minbleicoptguardsmoothness(state.innerobj, level, null);
+    }
+    
+    public static void minbleicoptguardsmoothness(minbleicstate state, int level, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicoptguardsmoothness(state.innerobj, level, _params);
+    }
+            
+    public static void minbleicoptguardsmoothness(minbleicstate state)
+    {
+        int level;
+    
+    
+        level = 1;
+        minbleic.minbleicoptguardsmoothness(state.innerobj, level, null);
+    
+        return;
+    }
+            
+    public static void minbleicoptguardsmoothness(minbleicstate state, alglib.xparams _params)
+    {
+        int level;
+    
+    
+        level = 1;
+        minbleic.minbleicoptguardsmoothness(state.innerobj, level, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Results of OptGuard integrity check, should be called  after  optimization
+    session is over.
+
+    === PRIMARY REPORT =======================================================
+
+    OptGuard performs several checks which are intended to catch common errors
+    in the implementation of nonlinear function/gradient:
+    * incorrect analytic gradient
+    * discontinuous (non-C0) target functions (constraints)
+    * nonsmooth     (non-C1) target functions (constraints)
+
+    Each of these checks is activated with appropriate function:
+    * minbleicoptguardgradient() for gradient verification
+    * minbleicoptguardsmoothness() for C0/C1 checks
+
+    Following flags are set when these errors are suspected:
+    * rep.badgradsuspected, and additionally:
+      * rep.badgradvidx for specific variable (gradient element) suspected
+      * rep.badgradxbase, a point where gradient is tested
+      * rep.badgraduser, user-provided gradient  (stored  as  2D  matrix  with
+        single row in order to make  report  structure  compatible  with  more
+        complex optimizers like MinNLC or MinLM)
+      * rep.badgradnum,   reference    gradient    obtained    via   numerical
+        differentiation (stored as  2D matrix with single row in order to make
+        report structure compatible with more complex optimizers  like  MinNLC
+        or MinLM)
+    * rep.nonc0suspected
+    * rep.nonc1suspected
+
+    === ADDITIONAL REPORTS/LOGS ==============================================
+
+    Several different tests are performed to catch C0/C1 errors, you can  find
+    out specific test signaled error by looking to:
+    * rep.nonc0test0positive, for non-C0 test #0
+    * rep.nonc1test0positive, for non-C1 test #0
+    * rep.nonc1test1positive, for non-C1 test #1
+
+    Additional information (including line search logs)  can  be  obtained  by
+    means of:
+    * minbleicoptguardnonc1test0results()
+    * minbleicoptguardnonc1test1results()
+    which return detailed error reports, specific points where discontinuities
+    were found, and so on.
+
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        rep     -   generic OptGuard report;  more  detailed  reports  can  be
+                    retrieved with other functions.
+
+    NOTE: false negatives (nonsmooth problems are not identified as  nonsmooth
+          ones) are possible although unlikely.
+
+          The reason  is  that  you  need  to  make several evaluations around
+          nonsmoothness  in  order  to  accumulate  enough  information  about
+          function curvature. Say, if you start right from the nonsmooth point,
+          optimizer simply won't get enough data to understand what  is  going
+          wrong before it terminates due to abrupt changes in the  derivative.
+          It is also  possible  that  "unlucky"  step  will  move  us  to  the
+          termination too quickly.
+
+          Our current approach is to have less than 0.1%  false  negatives  in
+          our test examples  (measured  with  multiple  restarts  from  random
+          points), and to have exactly 0% false positives.
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicoptguardresults(minbleicstate state, out optguardreport rep)
+    {
+        rep = new optguardreport();
+        minbleic.minbleicoptguardresults(state.innerobj, rep.innerobj, null);
+    }
+    
+    public static void minbleicoptguardresults(minbleicstate state, out optguardreport rep, alglib.xparams _params)
+    {
+        rep = new optguardreport();
+        minbleic.minbleicoptguardresults(state.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Detailed results of the OptGuard integrity check for nonsmoothness test #0
+
+    Nonsmoothness (non-C1) test #0 studies  function  values  (not  gradient!)
+    obtained during line searches and monitors  behavior  of  the  directional
+    derivative estimate.
+
+    This test is less powerful than test #1, but it does  not  depend  on  the
+    gradient values and thus it is more robust against artifacts introduced by
+    numerical differentiation.
+
+    Two reports are returned:
+    * a "strongest" one, corresponding  to  line   search  which  had  highest
+      value of the nonsmoothness indicator
+    * a "longest" one, corresponding to line search which  had  more  function
+      evaluations, and thus is more detailed
+
+    In both cases following fields are returned:
+
+    * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+      did not notice anything (in the latter cases fields below are empty).
+    * x0[], d[] - arrays of length N which store initial point  and  direction
+      for line search (d[] can be normalized, but does not have to)
+    * stp[], f[] - arrays of length CNT which store step lengths and  function
+      values at these points; f[i] is evaluated in x0+stp[i]*d.
+    * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+      between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+      with  most  likely  position  of  the  violation  between  stpidxa+1 and
+      stpidxa+2.
+
+    ==========================================================================
+    = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+    =                   see where C1 continuity is violated.
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        strrep  -   C1 test #0 "strong" report
+        lngrep  -   C1 test #0 "long" report
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicoptguardnonc1test0results(minbleicstate state, out optguardnonc1test0report strrep, out optguardnonc1test0report lngrep)
+    {
+        strrep = new optguardnonc1test0report();
+        lngrep = new optguardnonc1test0report();
+        minbleic.minbleicoptguardnonc1test0results(state.innerobj, strrep.innerobj, lngrep.innerobj, null);
+    }
+    
+    public static void minbleicoptguardnonc1test0results(minbleicstate state, out optguardnonc1test0report strrep, out optguardnonc1test0report lngrep, alglib.xparams _params)
+    {
+        strrep = new optguardnonc1test0report();
+        lngrep = new optguardnonc1test0report();
+        minbleic.minbleicoptguardnonc1test0results(state.innerobj, strrep.innerobj, lngrep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Detailed results of the OptGuard integrity check for nonsmoothness test #1
+
+    Nonsmoothness (non-C1)  test  #1  studies  individual  components  of  the
+    gradient computed during line search.
+
+    When precise analytic gradient is provided this test is more powerful than
+    test #0  which  works  with  function  values  and  ignores  user-provided
+    gradient.  However,  test  #0  becomes  more   powerful   when   numerical
+    differentiation is employed (in such cases test #1 detects  higher  levels
+    of numerical noise and becomes too conservative).
+
+    This test also tells specific components of the gradient which violate  C1
+    continuity, which makes it more informative than #0, which just tells that
+    continuity is violated.
+
+    Two reports are returned:
+    * a "strongest" one, corresponding  to  line   search  which  had  highest
+      value of the nonsmoothness indicator
+    * a "longest" one, corresponding to line search which  had  more  function
+      evaluations, and thus is more detailed
+
+    In both cases following fields are returned:
+
+    * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+      did not notice anything (in the latter cases fields below are empty).
+    * vidx - is an index of the variable in [0,N) with nonsmooth derivative
+    * x0[], d[] - arrays of length N which store initial point  and  direction
+      for line search (d[] can be normalized, but does not have to)
+    * stp[], g[] - arrays of length CNT which store step lengths and  gradient
+      values at these points; g[i] is evaluated in  x0+stp[i]*d  and  contains
+      vidx-th component of the gradient.
+    * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+      between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+      with  most  likely  position  of  the  violation  between  stpidxa+1 and
+      stpidxa+2.
+
+    ==========================================================================
+    = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+    =                   see where C1 continuity is violated.
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        strrep  -   C1 test #1 "strong" report
+        lngrep  -   C1 test #1 "long" report
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicoptguardnonc1test1results(minbleicstate state, out optguardnonc1test1report strrep, out optguardnonc1test1report lngrep)
+    {
+        strrep = new optguardnonc1test1report();
+        lngrep = new optguardnonc1test1report();
+        minbleic.minbleicoptguardnonc1test1results(state.innerobj, strrep.innerobj, lngrep.innerobj, null);
+    }
+    
+    public static void minbleicoptguardnonc1test1results(minbleicstate state, out optguardnonc1test1report strrep, out optguardnonc1test1report lngrep, alglib.xparams _params)
+    {
+        strrep = new optguardnonc1test1report();
+        lngrep = new optguardnonc1test1report();
+        minbleic.minbleicoptguardnonc1test1results(state.innerobj, strrep.innerobj, lngrep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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:
+                    * -8    internal integrity control  detected  infinite or
+                            NAN   values   in   function/gradient.   Abnormal
+                            termination signalled.
+                    * -3   inconsistent constraints. Feasible point is
+                           either nonexistent or too hard to find. Try to
+                           restart optimizer with better initial approximation
+                    *  1   relative function improvement is no more than EpsF.
+                    *  2   scaled step is no more than EpsX.
+                    *  4   scaled gradient norm is no more than EpsG.
+                    *  5   MaxIts steps was taken
+                    *  8   terminated by user who called minbleicrequesttermination().
+                           X contains point which was "current accepted"  when
+                           termination request was submitted.
+                    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, null);
+    }
+    
+    public static void minbleicresults(minbleicstate state, out double[] x, out minbleicreport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new minbleicreport();
+        minbleic.minbleicresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minbleicresultsbuf(minbleicstate state, ref double[] x, minbleicreport rep, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicresultsbuf(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minbleicrestartfrom(minbleicstate state, double[] x, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicrestartfrom(state.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine submits request for termination of running  optimizer.  It
+    should be called from user-supplied callback when user decides that it  is
+    time to "smoothly" terminate optimization process.  As  result,  optimizer
+    stops at point which was "current accepted" when termination  request  was
+    submitted and returns error code 8 (successful termination).
+
+    INPUT PARAMETERS:
+        State   -   optimizer structure
+
+    NOTE: after  request  for  termination  optimizer  may   perform   several
+          additional calls to user-supplied callbacks. It does  NOT  guarantee
+          to stop immediately - it just guarantees that these additional calls
+          will be discarded later.
+
+    NOTE: calling this function on optimizer which is NOT running will have no
+          effect.
+
+    NOTE: multiple calls to this function are possible. First call is counted,
+          subsequent calls are silently ignored.
+
+      -- ALGLIB --
+         Copyright 08.10.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbleicrequesttermination(minbleicstate state)
+    {
+    
+        minbleic.minbleicrequesttermination(state.innerobj, null);
+    }
+    
+    public static void minbleicrequesttermination(minbleicstate state, alglib.xparams _params)
+    {
+    
+        minbleic.minbleicrequesttermination(state.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+
+}
+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 : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public minqpstate()
+        {
+            _innerobj = new minqp.minqpstate();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minqpstate((minqp.minqpstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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:
+    * -9    failure of the automatic scale evaluation:  one  of  the  diagonal
+            elements of the quadratic term is non-positive.  Specify  variable
+            scales manually!
+    * -5    inappropriate solver was used:
+            * QuickQP solver for problem with general linear constraints (dense/sparse)
+    * -4    BLEIC-QP or QuickQP solver found unconstrained direction
+            of negative curvature (function is unbounded from
+            below  even  under  constraints),  no  meaningful
+            minimum can be found.
+    * -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
+    *  1..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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minqpreport((minqp.minqpreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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.
+
+    You should also choose appropriate QP solver and set it  and  its stopping
+    criteria by means of MinQPSetAlgo??????() function. Then, you should start
+    solution process by means of MinQPOptimize() call. Solution itself can  be
+    obtained with MinQPResults() function.
+
+    Following solvers are recommended:
+    * QuickQP for dense problems with box-only constraints (or no constraints
+      at all)
+    * QP-BLEIC for dense/sparse problems with moderate (up to 50) number of
+      general linear constraints
+    * DENSE-AUL-QP for dense problems with any (small or large) number of
+      general linear constraints
+
+    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, null);
+    }
+    
+    public static void minqpcreate(int n, out minqpstate state, alglib.xparams _params)
+    {
+        state = new minqpstate();
+        minqp.minqpcreate(n, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minqpsetlinearterm(minqpstate state, double[] b, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetlinearterm(state.innerobj, b, _params);
+    }
+    
+    /*************************************************************************
+    This  function  sets  dense  quadratic  term  for  QP solver. By  default,
+    quadratic term is zero.
+
+    SUPPORT BY QP SOLVERS:
+
+    Dense quadratic term can be handled by following QP solvers:
+    * QuickQP
+    * BLEIC-QP
+    * Dense-AUL-QP
+
+    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 isn't used
+                    * if False, symmetric matrix  A  is  given  by  its lower
+                      triangle, and the upper triangle isn't 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, null);
+    }
+    
+    public static void minqpsetquadraticterm(minqpstate state, double[,] a, bool isupper, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetquadraticterm(state.innerobj, a, isupper, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void minqpsetquadraticterm(minqpstate state, double[,] a, alglib.xparams _params)
+    {
+        bool isupper;
+        if( !alglib.ap.issymmetric(a) )
+            throw new alglibexception("'a' parameter is not symmetric matrix");
+    
+        isupper = false;
+        minqp.minqpsetquadraticterm(state.innerobj, a, isupper, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This  function  sets  sparse  quadratic  term  for  QP solver. By default,
+    quadratic  term  is  zero.  This  function  overrides  previous  calls  to
+    minqpsetquadraticterm() or minqpsetquadratictermsparse().
+
+    SUPPORT BY QP SOLVERS:
+
+    Sparse quadratic term can be handled by following QP solvers:
+    * QuickQP
+    * BLEIC-QP
+    * Dense-AUL-QP (internally converts sparse matrix to dense format)
+
+    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 isn't used
+                    * if False, symmetric matrix  A  is  given  by  its lower
+                      triangle, and the upper triangle isn't used
+                    * if not given, both lower and upper  triangles  must  be
+                      filled.
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetquadratictermsparse(minqpstate state, sparsematrix a, bool isupper)
+    {
+    
+        minqp.minqpsetquadratictermsparse(state.innerobj, a.innerobj, isupper, null);
+    }
+    
+    public static void minqpsetquadratictermsparse(minqpstate state, sparsematrix a, bool isupper, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetquadratictermsparse(state.innerobj, a.innerobj, isupper, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minqpsetstartingpoint(minqpstate state, double[] x, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetstartingpoint(state.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    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))
+
+    Specification of non-zero origin affects function being minimized, but not
+    constraints. Box and  linear  constraints  are  still  calculated  without
+    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, null);
+    }
+    
+    public static void minqpsetorigin(minqpstate state, double[] xorigin, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetorigin(state.innerobj, xorigin, _params);
+    }
+    
+    /*************************************************************************
+    This function sets scaling coefficients.
+
+    ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+    size and gradient are scaled before comparison  with  tolerances)  and  as
+    preconditioner.
+
+    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
+
+    If you do not know how to choose scales of your variables, you can:
+    * read www.alglib.net/optimization/scaling.php article
+    * use minqpsetscaleautodiag(), which calculates scale  using  diagonal  of
+      the  quadratic  term:  S  is  set to 1/sqrt(diag(A)), which works well
+      sometimes.
+
+    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 minqpsetscale(minqpstate state, double[] s)
+    {
+    
+        minqp.minqpsetscale(state.innerobj, s, null);
+    }
+    
+    public static void minqpsetscale(minqpstate state, double[] s, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetscale(state.innerobj, s, _params);
+    }
+    
+    /*************************************************************************
+    This function sets automatic evaluation of variable scaling.
+
+    IMPORTANT: this function works only for  matrices  with positive  diagonal
+               elements! Zero or negative elements will  result  in  -9  error
+               code  being  returned.  Specify  scale  vector  manually   with
+               minqpsetscale() in such cases.
+
+    ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+    size and gradient are scaled before comparison  with  tolerances)  and  as
+    preconditioner.
+
+    The  best  way  to  set  scaling  is  to manually specify variable scales.
+    However, sometimes you just need quick-and-dirty solution  -  either  when
+    you perform fast prototyping, or when you know your problem well  and  you
+    are 100% sure that this quick solution is robust enough in your case.
+
+    One such solution is to evaluate scale of I-th variable as 1/Sqrt(A[i,i]),
+    where A[i,i] is an I-th diagonal element of the quadratic term.
+
+    Such approach works well sometimes, but you have to be careful here.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+
+      -- ALGLIB --
+         Copyright 26.12.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetscaleautodiag(minqpstate state)
+    {
+    
+        minqp.minqpsetscaleautodiag(state.innerobj, null);
+    }
+    
+    public static void minqpsetscaleautodiag(minqpstate state, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetscaleautodiag(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function tells solver to use BLEIC-based algorithm and sets  stopping
+    criteria for the algorithm.
+
+    This algorithm is fast  enough  for large-scale  problems  with  following
+    properties:
+    a) feasible initial point, moderate amount of general linear constraints
+    b) arbitrary (can be infeasible) initial point, small  amount  of  general
+       linear constraints (say, hundred or less)
+
+    If you solve large-scale QP problem with many inequality  constraints  and
+    without initial feasibility guarantees, consider  using  DENSE-AUL  solver
+    instead. Initial feasibility detection stage by BLEIC may take too long on
+    such problems.
+
+    ALGORITHM FEATURES:
+
+    * supports dense and sparse QP problems
+    * supports box and general linear equality/inequality constraints
+    * can solve all types of problems  (convex,  semidefinite,  nonconvex)  as
+      long as they are bounded from below under constraints.
+      Say, it is possible to solve "min{-x^2} subject to -1<=x<=+1".
+      Of course, global  minimum  is found only  for  positive  definite   and
+      semidefinite  problems.  As  for indefinite ones - only local minimum is
+      found.
+
+    ALGORITHM OUTLINE:
+
+    * BLEIC-QP solver is just a driver function for MinBLEIC solver; it solves
+      quadratic  programming   problem   as   general   linearly   constrained
+      optimization problem, which is solved by means of BLEIC solver  (part of
+      ALGLIB, active set method).
+
+    ALGORITHM LIMITATIONS:
+    * This algorithm is inefficient on  problems with hundreds  and  thousands
+      of general inequality constraints and infeasible initial point.  Initial
+      feasibility detection stage may take too long on such constraint sets.
+      Consider using DENSE-AUL instead.
+    * unlike QuickQP solver, this algorithm does not perform Newton steps  and
+      does not use Level 3 BLAS. Being general-purpose active set  method,  it
+      can activate constraints only one-by-one. Thus, its performance is lower
+      than that of QuickQP.
+    * its precision is also a bit  inferior  to  that  of   QuickQP.  BLEIC-QP
+      performs only LBFGS steps (no Newton steps), which are good at detecting
+      neighborhood of the solution, buy needs many iterations to find solution
+      with more than 6 digits of precision.
+
+    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 constrained gradient vector, v[i]=g[i]*s[i]
+                    * g - gradient
+                    * s - scaling coefficients set by MinQPSetScale()
+        EpsF    -   >=0
+                    The  subroutine  finishes its work if exploratory steepest
+                    descent  step  on  k+1-th iteration  satisfies   following
+                    condition:  |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1}
+        EpsX    -   >=0
+                    The  subroutine  finishes its work if exploratory steepest
+                    descent  step  on  k+1-th iteration  satisfies   following
+                    condition:
+                    * |.| means Euclidian norm
+                    * v - scaled step vector, v[i]=dx[i]/s[i]
+                    * dx - step vector, dx=X(k+1)-X(k)
+                    * s - scaling coefficients set by MinQPSetScale()
+        MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                    iterations is unlimited. NOTE: this  algorithm uses  LBFGS
+                    iterations,  which  are  relatively  cheap,  but   improve
+                    function value only a bit. So you will need many iterations
+                    to converge - from 0.1*N to 10*N, depending  on  problem's
+                    condition number.
+
+    IT IS VERY IMPORTANT TO CALL MinQPSetScale() WHEN YOU USE THIS  ALGORITHM
+    BECAUSE ITS STOPPING CRITERIA ARE SCALE-DEPENDENT!
+
+    Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead
+    to automatic stopping criterion selection (presently it is  small    step
+    length, but it may change in the future versions of ALGLIB).
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetalgobleic(minqpstate state, double epsg, double epsf, double epsx, int maxits)
+    {
+    
+        minqp.minqpsetalgobleic(state.innerobj, epsg, epsf, epsx, maxits, null);
+    }
+    
+    public static void minqpsetalgobleic(minqpstate state, double epsg, double epsf, double epsx, int maxits, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetalgobleic(state.innerobj, epsg, epsf, epsx, maxits, _params);
+    }
+    
+    /*************************************************************************
+    This function tells QP solver to use Dense-AUL algorithm and sets stopping
+    criteria for the algorithm.
+
+    ALGORITHM FEATURES:
+
+    * supports  box  and  dense/sparse  general   linear   equality/inequality
+      constraints
+    * convergence is theoretically proved for positive-definite  (convex)   QP
+      problems. Semidefinite and non-convex problems can be solved as long  as
+      they  are   bounded  from  below  under  constraints,  although  without
+      theoretical guarantees.
+    * this solver is better than QP-BLEIC on problems  with  large  number  of
+      general linear constraints. It better handles infeasible initial points.
+
+    ALGORITHM OUTLINE:
+
+    * this  algorithm   is   an   augmented   Lagrangian   method  with  dense
+      preconditioner (hence  its  name).  It  is  similar  to  barrier/penalty
+      methods, but much more precise and faster.
+    * it performs several outer iterations in order to refine  values  of  the
+      Lagrange multipliers. Single outer  iteration  is  a  solution  of  some
+      unconstrained optimization problem: first  it  performs  dense  Cholesky
+      factorization of the Hessian in order to build preconditioner  (adaptive
+      regularization is applied to enforce positive  definiteness),  and  then
+      it uses L-BFGS optimizer to solve optimization problem.
+    * typically you need about 5-10 outer iterations to converge to solution
+
+    ALGORITHM LIMITATIONS:
+
+    * because dense Cholesky driver is used, this algorithm has O(N^2)  memory
+      requirements and O(OuterIterations*N^3) minimum running time.  From  the
+      practical  point  of  view,  it  limits  its  applicability  by  several
+      thousands of variables.
+      From  the  other  side,  variables  count  is  the most limiting factor,
+      and dependence on constraint count is  much  more  lower. Assuming  that
+      constraint matrix is sparse, it may handle tens of thousands  of general
+      linear constraints.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        EpsX    -   >=0, stopping criteria for inner optimizer.
+                    Inner  iterations  are  stopped  when  step  length  (with
+                    variable scaling being applied) is less than EpsX.
+                    See  minqpsetscale()  for  more  information  on  variable
+                    scaling.
+        Rho     -   penalty coefficient, Rho>0:
+                    * large enough  that  algorithm  converges  with   desired
+                      precision.
+                    * not TOO large to prevent ill-conditioning
+                    * recommended values are 100, 1000 or 10000
+        ItsCnt  -   number of outer iterations:
+                    * recommended values: 10-15 (although  in  most  cases  it
+                      converges within 5 iterations, you may need a  few  more
+                      to be sure).
+                    * ItsCnt=0 means that small number of outer iterations  is
+                      automatically chosen (10 iterations in current version).
+                    * ItsCnt=1 means that AUL algorithm performs just as usual
+                      penalty method.
+                    * ItsCnt>1 means that  AUL  algorithm  performs  specified
+                      number of outer iterations
+
+    IT IS VERY IMPORTANT TO CALL minqpsetscale() WHEN YOU USE THIS  ALGORITHM
+    BECAUSE ITS CONVERGENCE PROPERTIES AND STOPPING CRITERIA ARE SCALE-DEPENDENT!
+
+    NOTE: Passing  EpsX=0  will  lead  to  automatic  step  length  selection
+          (specific step length chosen may change in the future  versions  of
+          ALGLIB, so it is better to specify step length explicitly).
+
+      -- ALGLIB --
+         Copyright 20.08.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetalgodenseaul(minqpstate state, double epsx, double rho, int itscnt)
+    {
+    
+        minqp.minqpsetalgodenseaul(state.innerobj, epsx, rho, itscnt, null);
+    }
+    
+    public static void minqpsetalgodenseaul(minqpstate state, double epsx, double rho, int itscnt, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetalgodenseaul(state.innerobj, epsx, rho, itscnt, _params);
+    }
+    
+    /*************************************************************************
+    This function tells solver to use QuickQP  algorithm:  special  extra-fast
+    algorithm for problems with box-only constrants. It may  solve  non-convex
+    problems as long as they are bounded from below under constraints.
+
+    ALGORITHM FEATURES:
+    * many times (from 5x to 50x!) faster than BLEIC-based QP solver; utilizes
+      accelerated methods for activation of constraints.
+    * supports dense and sparse QP problems
+    * supports ONLY box constraints; general linear constraints are NOT
+      supported by this solver
+    * can solve all types of problems  (convex,  semidefinite,  nonconvex)  as
+      long as they are bounded from below under constraints.
+      Say, it is possible to solve "min{-x^2} subject to -1<=x<=+1".
+      In convex/semidefinite case global minimum  is  returned,  in  nonconvex
+      case - algorithm returns one of the local minimums.
+
+    ALGORITHM OUTLINE:
+
+    * algorithm  performs  two kinds of iterations: constrained CG  iterations
+      and constrained Newton iterations
+    * initially it performs small number of constrained CG  iterations,  which
+      can efficiently activate/deactivate multiple constraints
+    * after CG phase algorithm tries to calculate Cholesky  decomposition  and
+      to perform several constrained Newton steps. If  Cholesky  decomposition
+      failed (matrix is indefinite even under constraints),  we  perform  more
+      CG iterations until we converge to such set of constraints  that  system
+      matrix becomes  positive  definite.  Constrained  Newton  steps  greatly
+      increase convergence speed and precision.
+    * algorithm interleaves CG and Newton iterations which  allows  to  handle
+      indefinite matrices (CG phase) and quickly converge after final  set  of
+      constraints is found (Newton phase). Combination of CG and Newton phases
+      is called "outer iteration".
+    * it is possible to turn off Newton  phase  (beneficial  for  semidefinite
+      problems - Cholesky decomposition will fail too often)
+
+    ALGORITHM LIMITATIONS:
+
+    * algorithm does not support general  linear  constraints;  only  box ones
+      are supported
+    * Cholesky decomposition for sparse problems  is  performed  with  Skyline
+      Cholesky solver, which is intended for low-profile matrices. No profile-
+      reducing reordering of variables is performed in this version of ALGLIB.
+    * problems with near-zero negative eigenvalues (or exacty zero  ones)  may
+      experience about 2-3x performance penalty. The reason is  that  Cholesky
+      decomposition can not be performed until we identify directions of  zero
+      and negative curvature and activate corresponding boundary constraints -
+      but we need a lot of trial and errors because these directions  are hard
+      to notice in the matrix spectrum.
+      In this case you may turn off Newton phase of algorithm.
+      Large negative eigenvalues  are  not  an  issue,  so  highly  non-convex
+      problems can be solved very efficiently.
+
+    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 constrained gradient vector, v[i]=g[i]*s[i]
+                    * g - gradient
+                    * s - scaling coefficients set by MinQPSetScale()
+        EpsF    -   >=0
+                    The  subroutine  finishes its work if exploratory steepest
+                    descent  step  on  k+1-th iteration  satisfies   following
+                    condition:  |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1}
+        EpsX    -   >=0
+                    The  subroutine  finishes its work if exploratory steepest
+                    descent  step  on  k+1-th iteration  satisfies   following
+                    condition:
+                    * |.| means Euclidian norm
+                    * v - scaled step vector, v[i]=dx[i]/s[i]
+                    * dx - step vector, dx=X(k+1)-X(k)
+                    * s - scaling coefficients set by MinQPSetScale()
+        MaxOuterIts-maximum number of OUTER iterations.  One  outer  iteration
+                    includes some amount of CG iterations (from 5 to  ~N)  and
+                    one or several (usually small amount) Newton steps.  Thus,
+                    one outer iteration has high cost, but can greatly  reduce
+                    funcation value.
+                    Use 0 if you do not want to limit number of outer iterations.
+        UseNewton-  use Newton phase or not:
+                    * Newton phase improves performance of  positive  definite
+                      dense problems (about 2 times improvement can be observed)
+                    * can result in some performance penalty  on  semidefinite
+                      or slightly negative definite  problems  -  each  Newton
+                      phase will bring no improvement (Cholesky failure),  but
+                      still will require computational time.
+                    * if you doubt, you can turn off this  phase  -  optimizer
+                      will retain its most of its high speed.
+
+    IT IS VERY IMPORTANT TO CALL MinQPSetScale() WHEN YOU USE THIS  ALGORITHM
+    BECAUSE ITS STOPPING CRITERIA ARE SCALE-DEPENDENT!
+
+    Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead
+    to automatic stopping criterion selection (presently it is  small    step
+    length, but it may change in the future versions of ALGLIB).
+
+      -- ALGLIB --
+         Copyright 22.05.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetalgoquickqp(minqpstate state, double epsg, double epsf, double epsx, int maxouterits, bool usenewton)
+    {
+    
+        minqp.minqpsetalgoquickqp(state.innerobj, epsg, epsf, epsx, maxouterits, usenewton, null);
+    }
+    
+    public static void minqpsetalgoquickqp(minqpstate state, double epsg, double epsf, double epsx, int maxouterits, bool usenewton, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetalgoquickqp(state.innerobj, epsg, epsf, epsx, maxouterits, usenewton, _params);
+    }
+    
+    /*************************************************************************
+    This function sets box constraints for QP solver
+
+    Box constraints are inactive by default (after  initial  creation).  After
+    being  set,  they  are  preserved until explicitly turned off with another
+    SetBC() call.
+
+    All QP solvers may handle box constraints.
+
+    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, null);
+    }
+    
+    public static void minqpsetbc(minqpstate state, double[] bndl, double[] bndu, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetbc(state.innerobj, bndl, bndu, _params);
+    }
+    
+    /*************************************************************************
+    This function sets dense linear constraints for QP optimizer.
+
+    This  function  overrides  results  of  previous  calls  to  minqpsetlc(),
+    minqpsetlcsparse() and minqpsetlcmixed().  After  call  to  this  function
+    sparse constraints are dropped, and you have only those constraints  which
+    were specified in the present call.
+
+    If you want  to  specify  mixed  (with  dense  and  sparse  terms)  linear
+    constraints, you should call minqpsetlcmixed().
+
+    SUPPORT BY QP SOLVERS:
+
+    Following QP solvers can handle dense linear constraints:
+    * BLEIC-QP          -   handles them  with  high  precision,  but  may  be
+                            inefficient for problems with hundreds of constraints
+    * Dense-AUL-QP      -   handles them with moderate precision (approx. 10^-6),
+                            may efficiently handle thousands of constraints.
+
+    Following QP solvers can NOT handle dense linear constraints:
+    * QuickQP           -   can not handle general linear constraints
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with MinQPCreate 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 violation due  to  numerical  errors  and
+            algorithmic limitations (BLEIC-QP solver is most  precise,  AUL-QP
+            solver is less precise).
+
+      -- ALGLIB --
+         Copyright 19.06.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetlc(minqpstate state, double[,] c, int[] ct, int k)
+    {
+    
+        minqp.minqpsetlc(state.innerobj, c, ct, k, null);
+    }
+    
+    public static void minqpsetlc(minqpstate state, double[,] c, int[] ct, int k, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetlc(state.innerobj, c, ct, k, _params);
+    }
+            
+    public static void minqpsetlc(minqpstate state, double[,] c, int[] ct)
+    {
+        int k;
+        if( (ap.rows(c)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'minqpsetlc': looks like one of arguments has wrong size");
+    
+        k = ap.rows(c);
+        minqp.minqpsetlc(state.innerobj, c, ct, k, null);
+    
+        return;
+    }
+            
+    public static void minqpsetlc(minqpstate state, double[,] c, int[] ct, alglib.xparams _params)
+    {
+        int k;
+        if( (ap.rows(c)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'minqpsetlc': looks like one of arguments has wrong size");
+    
+        k = ap.rows(c);
+        minqp.minqpsetlc(state.innerobj, c, ct, k, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function sets sparse linear constraints for QP optimizer.
+
+    This  function  overrides  results  of  previous  calls  to  minqpsetlc(),
+    minqpsetlcsparse() and minqpsetlcmixed().  After  call  to  this  function
+    dense constraints are dropped, and you have only those  constraints  which
+    were specified in the present call.
+
+    If you want  to  specify  mixed  (with  dense  and  sparse  terms)  linear
+    constraints, you should call minqpsetlcmixed().
+
+    SUPPORT BY QP SOLVERS:
+
+    Following QP solvers can handle sparse linear constraints:
+    * BLEIC-QP          -   handles them  with  high  precision,  but can  not
+                            utilize their sparsity - sparse constraint  matrix
+                            is silently converted to dense  format.  Thus,  it
+                            may be inefficient for problems with  hundreds  of
+                            constraints.
+    * Dense-AUL-QP      -   although this solver uses dense linear algebra  to
+                            calculate   Cholesky   preconditioner,   it    may
+                            efficiently  handle  sparse  constraints.  It  may
+                            solve problems  with  hundreds  and  thousands  of
+                            constraints. The only drawback is  that  precision
+                            of constraint handling is typically within 1E-4...
+                            ..1E-6 range.
+
+    Following QP solvers can NOT handle sparse linear constraints:
+    * QuickQP           -   can not handle general linear constraints
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with MinQPCreate call.
+        C       -   linear  constraints,  sparse  matrix  with  dimensions  at
+                    least [K,N+1]. If matrix has  larger  size,  only  leading
+                    Kx(N+1) rectangle is used.
+                    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
+
+    NOTE 1: linear (non-bound) constraints are satisfied only approximately  -
+            there always exists some violation due  to  numerical  errors  and
+            algorithmic limitations (BLEIC-QP solver is most  precise,  AUL-QP
+            solver is less precise).
+
+      -- ALGLIB --
+         Copyright 22.08.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetlcsparse(minqpstate state, sparsematrix c, int[] ct, int k)
+    {
+    
+        minqp.minqpsetlcsparse(state.innerobj, c.innerobj, ct, k, null);
+    }
+    
+    public static void minqpsetlcsparse(minqpstate state, sparsematrix c, int[] ct, int k, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetlcsparse(state.innerobj, c.innerobj, ct, k, _params);
+    }
+    
+    /*************************************************************************
+    This function sets mixed linear constraints, which include a set of  dense
+    rows, and a set of sparse rows.
+
+    This  function  overrides  results  of  previous  calls  to  minqpsetlc(),
+    minqpsetlcsparse() and minqpsetlcmixed().
+
+    This function may be useful if constraint matrix includes large number  of
+    both types of rows - dense and sparse. If you have just a few sparse rows,
+    you  may  represent  them  in  dense  format  without loosing performance.
+    Similarly, if you have just a few dense rows, you may store them in sparse
+    format with almost same performance.
+
+    SUPPORT BY QP SOLVERS:
+
+    Following QP solvers can handle mixed dense/sparse linear constraints:
+    * BLEIC-QP          -   handles them  with  high  precision,  but can  not
+                            utilize their sparsity - sparse constraint  matrix
+                            is silently converted to dense  format.  Thus,  it
+                            may be inefficient for problems with  hundreds  of
+                            constraints.
+    * Dense-AUL-QP      -   although this solver uses dense linear algebra  to
+                            calculate   Cholesky   preconditioner,   it    may
+                            efficiently  handle  sparse  constraints.  It  may
+                            solve problems  with  hundreds  and  thousands  of
+                            constraints. The only drawback is  that  precision
+                            of constraint handling is typically within 1E-4...
+                            ..1E-6 range.
+
+    Following QP solvers can NOT handle mixed linear constraints:
+    * QuickQP           -   can not handle general linear constraints at all
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with MinQPCreate call.
+        DenseC  -   dense linear constraints, array[K,N+1].
+                    Each row of DenseC 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 DenseC (including right part) must be finite.
+        DenseCT -   type of constraints, array[K]:
+                    * if DenseCT[i]>0, then I-th constraint is DenseC[i,*]*x >= DenseC[i,n+1]
+                    * if DenseCT[i]=0, then I-th constraint is DenseC[i,*]*x  = DenseC[i,n+1]
+                    * if DenseCT[i]<0, then I-th constraint is DenseC[i,*]*x <= DenseC[i,n+1]
+        DenseK  -   number of equality/inequality constraints, DenseK>=0
+        SparseC -   linear  constraints,  sparse  matrix  with  dimensions  at
+                    least [SparseK,N+1]. If matrix has  larger  size,  only  leading
+                    SPARSEKx(N+1) rectangle is used.
+                    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.
+        SparseCT-   type of sparse constraints, array[K]:
+                    * if SparseCT[i]>0, then I-th constraint is SparseC[i,*]*x >= SparseC[i,n+1]
+                    * if SparseCT[i]=0, then I-th constraint is SparseC[i,*]*x  = SparseC[i,n+1]
+                    * if SparseCT[i]<0, then I-th constraint is SparseC[i,*]*x <= SparseC[i,n+1]
+        SparseK -   number of sparse equality/inequality constraints, K>=0
+
+    NOTE 1: linear (non-bound) constraints are satisfied only approximately  -
+            there always exists some violation due  to  numerical  errors  and
+            algorithmic limitations (BLEIC-QP solver is most  precise,  AUL-QP
+            solver is less precise).
+
+      -- ALGLIB --
+         Copyright 22.08.2016 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minqpsetlcmixed(minqpstate state, double[,] densec, int[] densect, int densek, sparsematrix sparsec, int[] sparsect, int sparsek)
+    {
+    
+        minqp.minqpsetlcmixed(state.innerobj, densec, densect, densek, sparsec.innerobj, sparsect, sparsek, null);
+    }
+    
+    public static void minqpsetlcmixed(minqpstate state, double[,] densec, int[] densect, int densek, sparsematrix sparsec, int[] sparsect, int sparsek, alglib.xparams _params)
+    {
+    
+        minqp.minqpsetlcmixed(state.innerobj, densec, densect, densek, sparsec.innerobj, sparsect, sparsek, _params);
+    }
+    
+    /*************************************************************************
+    This function solves quadratic programming problem.
+
+    Prior to calling this function you should choose solver by means of one of
+    the following functions:
+
+    * minqpsetalgoquickqp()     - for QuickQP solver
+    * minqpsetalgobleic()       - for BLEIC-QP solver
+    * minqpsetalgodenseaul()    - for Dense-AUL-QP solver
+
+    These functions also allow you to control stopping criteria of the solver.
+    If you did not set solver,  MinQP  subpackage  will  automatically  select
+    solver for your problem and will run it with default stopping criteria.
+
+    However, it is better to set explicitly solver and its stopping criteria.
+
+    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.
+         Special thanks to Elvira Illarionova  for  important  suggestions  on
+         the linearly constrained QP algorithm.
+    *************************************************************************/
+    public static void minqpoptimize(minqpstate state)
+    {
+    
+        minqp.minqpoptimize(state.innerobj, null);
+    }
+    
+    public static void minqpoptimize(minqpstate state, alglib.xparams _params)
+    {
+    
+        minqp.minqpoptimize(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    QP solver results
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[0..N-1], solution.
+                    This array is allocated and initialized only when
+                    Rep.TerminationType parameter is positive (success).
+        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.
+
+                    Failure codes returned by algorithm are:
+                    * -9    failure of the automatic scale evaluation:  one of
+                            the diagonal elements of  the  quadratic  term  is
+                            non-positive.  Specify variable scales manually!
+                    * -5    inappropriate solver was used:
+                            * QuickQP solver for problem with  general  linear
+                              constraints
+                    * -4    BLEIC-QP/QuickQP   solver    found   unconstrained
+                            direction  of   negative  curvature  (function  is
+                            unbounded from below even under constraints),   no
+                            meaningful minimum can be found.
+                    * -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.
+
+                    Completion codes specific for Cholesky algorithm:
+                    *  4   successful completion
+
+                    Completion codes specific for BLEIC/QuickQP algorithms:
+                    *  1   relative function improvement is no more than EpsF.
+                    *  2   scaled step is no more than EpsX.
+                    *  4   scaled gradient norm is no more than EpsG.
+                    *  5   MaxIts steps was taken
+
+      -- 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, null);
+    }
+    
+    public static void minqpresults(minqpstate state, out double[] x, out minqpreport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new minqpreport();
+        minqp.minqpresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minqpresultsbuf(minqpstate state, ref double[] x, minqpreport rep, alglib.xparams _params)
+    {
+    
+        minqp.minqpresultsbuf(state.innerobj, ref x, rep.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This object stores linear solver state.
+    You should use functions provided by MinLP subpackage to work with this
+    object
+    *************************************************************************/
+    public class minlpstate : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public minlpstate()
+        {
+            _innerobj = new minlp.minlpstate();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minlpstate((minlp.minlpstate)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minlp.minlpstate _innerobj;
+        public minlp.minlpstate innerobj { get { return _innerobj; } }
+        public minlpstate(minlp.minlpstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This structure stores optimization report:
+    * f                         target function value
+    * y                         dual variables
+    * stats                     array[N+M], statuses of box (N) and linear (M)
+                                constraints:
+                                * stats[i]>0  =>  constraint at upper bound
+                                                  (also used for free non-basic
+                                                  variables set to zero)
+                                * stats[i]<0  =>  constraint at lower bound
+                                * stats[i]=0  =>  constraint is inactive, basic
+                                                  variable
+    * primalerror               primal feasibility error
+    * dualerror                 dual feasibility error
+    * iterationscount           iteration count
+    * terminationtype           completion code (see below)
+
+    Completion codes:
+    * -4    LP problem is primal unbounded (dual infeasible)
+    * -3    LP problem is primal infeasible (dual unbounded)
+    *  1..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 minlpreport : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public double f { get { return _innerobj.f; } set { _innerobj.f = value; } }
+        public double[] y { get { return _innerobj.y; } set { _innerobj.y = value; } }
+        public int[] stats { get { return _innerobj.stats; } set { _innerobj.stats = value; } }
+        public double primalerror { get { return _innerobj.primalerror; } set { _innerobj.primalerror = value; } }
+        public double dualerror { get { return _innerobj.dualerror; } set { _innerobj.dualerror = value; } }
+        public int iterationscount { get { return _innerobj.iterationscount; } set { _innerobj.iterationscount = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+    
+        public minlpreport()
+        {
+            _innerobj = new minlp.minlpreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minlpreport((minlp.minlpreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minlp.minlpreport _innerobj;
+        public minlp.minlpreport innerobj { get { return _innerobj; } }
+        public minlpreport(minlp.minlpreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+    
+    /*************************************************************************
+                                LINEAR PROGRAMMING
+
+    The subroutine creates LP  solver.  After  initial  creation  it  contains
+    default optimization problem with zero cost vector and all variables being
+    fixed to zero values and no constraints.
+
+    In order to actually solve something you should:
+    * set cost vector with minlpsetcost()
+    * set variable bounds with minlpsetbc() or minlpsetbcall()
+    * specify constraint matrix with one of the following functions:
+      [*] minlpsetlc()        for dense one-sided constraints
+      [*] minlpsetlc2dense()  for dense two-sided constraints
+      [*] minlpsetlc2()       for sparse two-sided constraints
+      [*] minlpaddlc2dense()  to add one dense row to constraint matrix
+      [*] minlpaddlc2()       to add one row to constraint matrix (compressed format)
+    * call minlpoptimize() to run the solver and  minlpresults()  to  get  the
+      solution vector and additional information.
+
+    Presently  this  optimizer  supports  only  revised  simplex   method   as
+    underlying solver. DSE pricing and bounds flipping ratio  test  (aka  long
+    dual step) are supported. Large-scale sparse LU solver with  Forest-Tomlin
+    is used internally as linear algebra driver.
+
+    Future releases of ALGLIB may introduce other solvers.
+
+    INPUT PARAMETERS:
+        N       -   problem size
+
+    OUTPUT PARAMETERS:
+        State   -   optimizer in the default state
+
+      -- ALGLIB --
+         Copyright 19.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlpcreate(int n, out minlpstate state)
+    {
+        state = new minlpstate();
+        minlp.minlpcreate(n, state.innerobj, null);
+    }
+    
+    public static void minlpcreate(int n, out minlpstate state, alglib.xparams _params)
+    {
+        state = new minlpstate();
+        minlp.minlpcreate(n, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function sets cost term for LP solver.
+
+    By default, cost term is zero.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        C       -   cost term, array[N].
+
+      -- ALGLIB --
+         Copyright 19.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlpsetcost(minlpstate state, double[] c)
+    {
+    
+        minlp.minlpsetcost(state.innerobj, c, null);
+    }
+    
+    public static void minlpsetcost(minlpstate state, double[] c, alglib.xparams _params)
+    {
+    
+        minlp.minlpsetcost(state.innerobj, c, _params);
+    }
+    
+    /*************************************************************************
+    This function sets scaling coefficients.
+
+    ALGLIB optimizers use scaling matrices to test stopping  conditions and as
+    preconditioner.
+
+    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
+
+    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 19.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlpsetscale(minlpstate state, double[] s)
+    {
+    
+        minlp.minlpsetscale(state.innerobj, s, null);
+    }
+    
+    public static void minlpsetscale(minlpstate state, double[] s, alglib.xparams _params)
+    {
+    
+        minlp.minlpsetscale(state.innerobj, s, _params);
+    }
+    
+    /*************************************************************************
+    This function sets box constraints for LP solver (all variables  at  once,
+    different constraints for different variables).
+
+    The default state of constraints is to have all variables fixed  at  zero.
+    You have to overwrite it by your own constraint vector. Constraint  status
+    is preserved until constraints are  explicitly  overwritten  with  another
+    minlpsetbc()  call,   overwritten   with  minlpsetbcall(),  or   partially
+    overwritten with minlmsetbci() call.
+
+    Following types of constraints are supported:
+
+        DESCRIPTION         CONSTRAINT              HOW TO SPECIFY
+        fixed variable      x[i]=Bnd[i]             BndL[i]=BndU[i]
+        lower bound         BndL[i]<=x[i]           BndU[i]=+INF
+        upper bound         x[i]<=BndU[i]           BndL[i]=-INF
+        range               BndL[i]<=x[i]<=BndU[i]  ...
+        free variable       -                       BndL[I]=-INF, BndU[I]+INF
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        BndL    -   lower bounds, array[N].
+        BndU    -   upper bounds, array[N].
+
+    NOTE: infinite values can be specified by means of Double.PositiveInfinity
+          and  Double.NegativeInfinity  (in  C#)  and  alglib::fp_posinf   and
+          alglib::fp_neginf (in C++).
+
+    NOTE: you may replace infinities by very small/very large values,  but  it
+          is not recommended because large numbers may introduce large numerical
+          errors in the algorithm.
+
+    NOTE: if constraints for all variables are same you may use minlpsetbcall()
+          which allows to specify constraints without using arrays.
+
+    NOTE: BndL>BndU will result in LP problem being recognized as infeasible.
+
+      -- ALGLIB --
+         Copyright 19.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlpsetbc(minlpstate state, double[] bndl, double[] bndu)
+    {
+    
+        minlp.minlpsetbc(state.innerobj, bndl, bndu, null);
+    }
+    
+    public static void minlpsetbc(minlpstate state, double[] bndl, double[] bndu, alglib.xparams _params)
+    {
+    
+        minlp.minlpsetbc(state.innerobj, bndl, bndu, _params);
+    }
+    
+    /*************************************************************************
+    This function sets box constraints for LP solver (all variables  at  once,
+    same constraints for all variables)
+
+    The default state of constraints is to have all variables fixed  at  zero.
+    You have to overwrite it by your own constraint vector. Constraint  status
+    is preserved until constraints are  explicitly  overwritten  with  another
+    minlpsetbc() call or partially overwritten with minlpsetbcall().
+
+    Following types of constraints are supported:
+
+        DESCRIPTION         CONSTRAINT              HOW TO SPECIFY
+        fixed variable      x[i]=Bnd[i]             BndL[i]=BndU[i]
+        lower bound         BndL[i]<=x[i]           BndU[i]=+INF
+        upper bound         x[i]<=BndU[i]           BndL[i]=-INF
+        range               BndL[i]<=x[i]<=BndU[i]  ...
+        free variable       -                       BndL[I]=-INF, BndU[I]+INF
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        BndL    -   lower bound, same for all variables
+        BndU    -   upper bound, same for all variables
+
+    NOTE: infinite values can be specified by means of Double.PositiveInfinity
+          and  Double.NegativeInfinity  (in  C#)  and  alglib::fp_posinf   and
+          alglib::fp_neginf (in C++).
+
+    NOTE: you may replace infinities by very small/very large values,  but  it
+          is not recommended because large numbers may introduce large numerical
+          errors in the algorithm.
+
+    NOTE: minlpsetbc() can  be  used  to  specify  different  constraints  for
+          different variables.
+
+    NOTE: BndL>BndU will result in LP problem being recognized as infeasible.
+
+      -- ALGLIB --
+         Copyright 19.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlpsetbcall(minlpstate state, double bndl, double bndu)
+    {
+    
+        minlp.minlpsetbcall(state.innerobj, bndl, bndu, null);
+    }
+    
+    public static void minlpsetbcall(minlpstate state, double bndl, double bndu, alglib.xparams _params)
+    {
+    
+        minlp.minlpsetbcall(state.innerobj, bndl, bndu, _params);
+    }
+    
+    /*************************************************************************
+    This function sets box constraints for I-th variable (other variables are
+    not modified).
+
+    The default state of constraints is to have all variables fixed  at  zero.
+    You have to overwrite it by your own constraint vector.
+
+    Following types of constraints are supported:
+
+        DESCRIPTION         CONSTRAINT              HOW TO SPECIFY
+        fixed variable      x[i]=Bnd[i]             BndL[i]=BndU[i]
+        lower bound         BndL[i]<=x[i]           BndU[i]=+INF
+        upper bound         x[i]<=BndU[i]           BndL[i]=-INF
+        range               BndL[i]<=x[i]<=BndU[i]  ...
+        free variable       -                       BndL[I]=-INF, BndU[I]+INF
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        I       -   variable index, in [0,N)
+        BndL    -   lower bound for I-th variable
+        BndU    -   upper bound for I-th variable
+
+    NOTE: infinite values can be specified by means of Double.PositiveInfinity
+          and  Double.NegativeInfinity  (in  C#)  and  alglib::fp_posinf   and
+          alglib::fp_neginf (in C++).
+
+    NOTE: you may replace infinities by very small/very large values,  but  it
+          is not recommended because large numbers may introduce large numerical
+          errors in the algorithm.
+
+    NOTE: minlpsetbc() can  be  used  to  specify  different  constraints  for
+          different variables.
+
+    NOTE: BndL>BndU will result in LP problem being recognized as infeasible.
+
+      -- ALGLIB --
+         Copyright 19.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlpsetbci(minlpstate state, int i, double bndl, double bndu)
+    {
+    
+        minlp.minlpsetbci(state.innerobj, i, bndl, bndu, null);
+    }
+    
+    public static void minlpsetbci(minlpstate state, int i, double bndl, double bndu, alglib.xparams _params)
+    {
+    
+        minlp.minlpsetbci(state.innerobj, i, bndl, bndu, _params);
+    }
+    
+    /*************************************************************************
+    This function sets one-sided linear constraints A*x ~ AU, where "~" can be
+    a mix of "<=", "=" and ">=".
+
+    IMPORTANT: this function is provided here for compatibility with the  rest
+               of ALGLIB optimizers which accept constraints  in  format  like
+               this one. Many real-life problems feature two-sided constraints
+               like a0 <= a*x <= a1. It is really inefficient to add them as a
+               pair of one-sided constraints.
+
+               Use minlpsetlc2dense(), minlpsetlc2(), minlpaddlc2()  (or   its
+               sparse version) wherever possible.
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with minlpcreate() call.
+        A       -   linear constraints, array[K,N+1]. Each row of A represents
+                    one constraint, with first N elements being linear coefficients,
+                    and last element being right side.
+        CT      -   constraint types, array[K]:
+                    * if CT[i]>0, then I-th constraint is A[i,*]*x >= A[i,n]
+                    * if CT[i]=0, then I-th constraint is A[i,*]*x  = A[i,n]
+                    * if CT[i]<0, then I-th constraint is A[i,*]*x <= A[i,n]
+        K       -   number of equality/inequality constraints,  K>=0;  if  not
+                    given, inferred from sizes of A and CT.
+
+      -- ALGLIB --
+         Copyright 19.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlpsetlc(minlpstate state, double[,] a, int[] ct, int k)
+    {
+    
+        minlp.minlpsetlc(state.innerobj, a, ct, k, null);
+    }
+    
+    public static void minlpsetlc(minlpstate state, double[,] a, int[] ct, int k, alglib.xparams _params)
+    {
+    
+        minlp.minlpsetlc(state.innerobj, a, ct, k, _params);
+    }
+            
+    public static void minlpsetlc(minlpstate state, double[,] a, int[] ct)
+    {
+        int k;
+        if( (ap.rows(a)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'minlpsetlc': looks like one of arguments has wrong size");
+    
+        k = ap.rows(a);
+        minlp.minlpsetlc(state.innerobj, a, ct, k, null);
+    
+        return;
+    }
+            
+    public static void minlpsetlc(minlpstate state, double[,] a, int[] ct, alglib.xparams _params)
+    {
+        int k;
+        if( (ap.rows(a)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'minlpsetlc': looks like one of arguments has wrong size");
+    
+        k = ap.rows(a);
+        minlp.minlpsetlc(state.innerobj, a, ct, k, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function sets two-sided linear constraints AL <= A*x <= AU.
+
+    This version accepts dense matrix as  input;  internally  LP  solver  uses
+    sparse storage  anyway  (most  LP  problems  are  sparse),  but  for  your
+    convenience it may accept dense inputs. This  function  overwrites  linear
+    constraints set by previous calls (if such calls were made).
+
+    We recommend you to use sparse version of this function unless  you  solve
+    small-scale LP problem (less than few hundreds of variables).
+
+    NOTE: there also exist several versions of this function:
+          * one-sided dense version which  accepts  constraints  in  the  same
+            format as one used by QP and  NLP solvers
+          * two-sided sparse version which accepts sparse matrix
+          * two-sided dense  version which allows you to add constraints row by row
+          * two-sided sparse version which allows you to add constraints row by row
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with minlpcreate() call.
+        A       -   linear constraints, array[K,N]. Each row of  A  represents
+                    one  constraint. One-sided  inequality   constraints, two-
+                    sided inequality  constraints,  equality  constraints  are
+                    supported (see below)
+        AL, AU  -   lower and upper bounds, array[K];
+                    * AL[i]=AU[i] => equality constraint Ai*x
+                    * AL[i]<AU[i] => two-sided constraint AL[i]<=Ai*x<=AU[i]
+                    * AL[i]=-INF  => one-sided constraint Ai*x<=AU[i]
+                    * AU[i]=+INF  => one-sided constraint AL[i]<=Ai*x
+                    * AL[i]=-INF, AU[i]=+INF => constraint is ignored
+        K       -   number of equality/inequality constraints,  K>=0;  if  not
+                    given, inferred from sizes of A, AL, AU.
+
+      -- ALGLIB --
+         Copyright 19.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlpsetlc2dense(minlpstate state, double[,] a, double[] al, double[] au, int k)
+    {
+    
+        minlp.minlpsetlc2dense(state.innerobj, a, al, au, k, null);
+    }
+    
+    public static void minlpsetlc2dense(minlpstate state, double[,] a, double[] al, double[] au, int k, alglib.xparams _params)
+    {
+    
+        minlp.minlpsetlc2dense(state.innerobj, a, al, au, k, _params);
+    }
+            
+    public static void minlpsetlc2dense(minlpstate state, double[,] a, double[] al, double[] au)
+    {
+        int k;
+        if( (ap.rows(a)!=ap.len(al)) || (ap.rows(a)!=ap.len(au)))
+            throw new alglibexception("Error while calling 'minlpsetlc2dense': looks like one of arguments has wrong size");
+    
+        k = ap.rows(a);
+        minlp.minlpsetlc2dense(state.innerobj, a, al, au, k, null);
+    
+        return;
+    }
+            
+    public static void minlpsetlc2dense(minlpstate state, double[,] a, double[] al, double[] au, alglib.xparams _params)
+    {
+        int k;
+        if( (ap.rows(a)!=ap.len(al)) || (ap.rows(a)!=ap.len(au)))
+            throw new alglibexception("Error while calling 'minlpsetlc2dense': looks like one of arguments has wrong size");
+    
+        k = ap.rows(a);
+        minlp.minlpsetlc2dense(state.innerobj, a, al, au, k, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This  function  sets  two-sided linear  constraints  AL <= A*x <= AU  with
+    sparse constraining matrix A. Recommended for large-scale problems.
+
+    This  function  overwrites  linear  (non-box)  constraints set by previous
+    calls (if such calls were made).
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with minlpcreate() call.
+        A       -   sparse matrix with size [K,N] (exactly!).
+                    Each row of A represents one general linear constraint.
+                    A can be stored in any sparse storage format.
+        AL, AU  -   lower and upper bounds, array[K];
+                    * AL[i]=AU[i] => equality constraint Ai*x
+                    * AL[i]<AU[i] => two-sided constraint AL[i]<=Ai*x<=AU[i]
+                    * AL[i]=-INF  => one-sided constraint Ai*x<=AU[i]
+                    * AU[i]=+INF  => one-sided constraint AL[i]<=Ai*x
+                    * AL[i]=-INF, AU[i]=+INF => constraint is ignored
+        K       -   number  of equality/inequality constraints, K>=0.  If  K=0
+                    is specified, A, AL, AU are ignored.
+
+      -- ALGLIB --
+         Copyright 19.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlpsetlc2(minlpstate state, sparsematrix a, double[] al, double[] au, int k)
+    {
+    
+        minlp.minlpsetlc2(state.innerobj, a.innerobj, al, au, k, null);
+    }
+    
+    public static void minlpsetlc2(minlpstate state, sparsematrix a, double[] al, double[] au, int k, alglib.xparams _params)
+    {
+    
+        minlp.minlpsetlc2(state.innerobj, a.innerobj, al, au, k, _params);
+    }
+    
+    /*************************************************************************
+    This function appends two-sided linear constraint  AL <= A*x <= AU  to the
+    list of currently present constraints.
+
+    This version accepts dense constraint vector as input, but  sparsifies  it
+    for internal storage and processing. Thus, time to add one  constraint  in
+    is O(N) - we have to scan entire array of length N. Sparse version of this
+    function is order of magnitude faster for  constraints  with  just  a  few
+    nonzeros per row.
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with minlpcreate() call.
+        A       -   linear constraint coefficient, array[N], right side is NOT
+                    included.
+        AL, AU  -   lower and upper bounds;
+                    * AL=AU    => equality constraint Ai*x
+                    * AL<AU    => two-sided constraint AL<=A*x<=AU
+                    * AL=-INF  => one-sided constraint Ai*x<=AU
+                    * AU=+INF  => one-sided constraint AL<=Ai*x
+                    * AL=-INF, AU=+INF => constraint is ignored
+
+      -- ALGLIB --
+         Copyright 19.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlpaddlc2dense(minlpstate state, double[] a, double al, double au)
+    {
+    
+        minlp.minlpaddlc2dense(state.innerobj, a, al, au, null);
+    }
+    
+    public static void minlpaddlc2dense(minlpstate state, double[] a, double al, double au, alglib.xparams _params)
+    {
+    
+        minlp.minlpaddlc2dense(state.innerobj, a, al, au, _params);
+    }
+    
+    /*************************************************************************
+    This function appends two-sided linear constraint  AL <= A*x <= AU  to the
+    list of currently present constraints.
+
+    Constraint is passed in compressed format: as list of non-zero entries  of
+    coefficient vector A. Such approach is more efficient than  dense  storage
+    for highly sparse constraint vectors.
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with minlpcreate() call.
+        IdxA    -   array[NNZ], indexes of non-zero elements of A:
+                    * can be unsorted
+                    * can include duplicate indexes (corresponding entries  of
+                      ValA[] will be summed)
+        ValA    -   array[NNZ], values of non-zero elements of A
+        NNZ     -   number of non-zero coefficients in A
+        AL, AU  -   lower and upper bounds;
+                    * AL=AU    => equality constraint A*x
+                    * AL<AU    => two-sided constraint AL<=A*x<=AU
+                    * AL=-INF  => one-sided constraint A*x<=AU
+                    * AU=+INF  => one-sided constraint AL<=A*x
+                    * AL=-INF, AU=+INF => constraint is ignored
+
+      -- ALGLIB --
+         Copyright 19.07.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlpaddlc2(minlpstate state, int[] idxa, double[] vala, int nnz, double al, double au)
+    {
+    
+        minlp.minlpaddlc2(state.innerobj, idxa, vala, nnz, al, au, null);
+    }
+    
+    public static void minlpaddlc2(minlpstate state, int[] idxa, double[] vala, int nnz, double al, double au, alglib.xparams _params)
+    {
+    
+        minlp.minlpaddlc2(state.innerobj, idxa, vala, nnz, al, au, _params);
+    }
+    
+    /*************************************************************************
+    This function solves LP problem.
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    You should use minlpresults() function to access results  after  calls  to
+    this function.
+
+      -- ALGLIB --
+         Copyright 19.07.2018 by Bochkanov Sergey.
+    *************************************************************************/
+    public static void minlpoptimize(minlpstate state)
+    {
+    
+        minlp.minlpoptimize(state.innerobj, null);
+    }
+    
+    public static void minlpoptimize(minlpstate state, alglib.xparams _params)
+    {
+    
+        minlp.minlpoptimize(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    LP solver results
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[N], solution. Filled by zeros on failure.
+        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.
+
+                    Failure codes returned by algorithm are:
+                    * -4    LP problem is primal unbounded (dual infeasible)
+                    * -3    LP problem is primal infeasible (dual unbounded)
+
+                    Success codes:
+                    *  1..4 successful completion
+                    *  5    MaxIts steps was taken
+
+      -- ALGLIB --
+         Copyright 11.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlpresults(minlpstate state, out double[] x, out minlpreport rep)
+    {
+        x = new double[0];
+        rep = new minlpreport();
+        minlp.minlpresults(state.innerobj, ref x, rep.innerobj, null);
+    }
+    
+    public static void minlpresults(minlpstate state, out double[] x, out minlpreport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new minlpreport();
+        minlp.minlpresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    LP results
+
+    Buffered implementation of MinLPResults() 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 minlpresultsbuf(minlpstate state, ref double[] x, minlpreport rep)
+    {
+    
+        minlp.minlpresultsbuf(state.innerobj, ref x, rep.innerobj, null);
+    }
+    
+    public static void minlpresultsbuf(minlpstate state, ref double[] x, minlpreport rep, alglib.xparams _params)
+    {
+    
+        minlp.minlpresultsbuf(state.innerobj, ref x, rep.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This object stores nonlinear optimizer state.
+    You should use functions provided by MinNLC subpackage to work  with  this
+    object
+    *************************************************************************/
+    public class minnlcstate : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        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[,] j { get { return _innerobj.j; } }
+        public double[] x { get { return _innerobj.x; } }
+    
+        public minnlcstate()
+        {
+            _innerobj = new minnlc.minnlcstate();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minnlcstate((minnlc.minnlcstate)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minnlc.minnlcstate _innerobj;
+        public minnlc.minnlcstate innerobj { get { return _innerobj; } }
+        public minnlcstate(minnlc.minnlcstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    These fields store optimization report:
+    * iterationscount           total number of inner iterations
+    * nfev                      number of gradient evaluations
+    * terminationtype           termination type (see below)
+
+    Scaled constraint violations are reported:
+    * bcerr                     maximum violation of the box constraints
+    * bcidx                     index of the most violated box  constraint (or
+                                -1, if all box constraints  are  satisfied  or
+                                there is no box constraint)
+    * lcerr                     maximum violation of the  linear  constraints,
+                                computed as maximum  scaled  distance  between
+                                final point and constraint boundary.
+    * lcidx                     index of the most violated  linear  constraint
+                                (or -1, if all constraints  are  satisfied  or
+                                there is no general linear constraints)
+    * nlcerr                    maximum violation of the nonlinear constraints
+    * nlcidx                    index of the most violated nonlinear constraint
+                                (or -1, if all constraints  are  satisfied  or
+                                there is no nonlinear constraints)
+
+    Violations of box constraints are scaled on per-component basis  according
+    to  the  scale  vector s[] as specified by minnlcsetscale(). Violations of
+    the general linear  constraints  are  also  computed  using  user-supplied
+    variable scaling. Violations of nonlinear constraints are computed "as is"
+
+    TERMINATION CODES
+
+    TerminationType field contains completion code, which can be either:
+
+    === FAILURE CODE ===
+      -8    internal integrity control detected  infinite  or  NAN  values  in
+            function/gradient. Abnormal termination signaled.
+      -3    box  constraints  are  infeasible.  Note: infeasibility of non-box
+            constraints does NOT trigger emergency  completion;  you  have  to
+            examine  bcerr/lcerr/nlcerr   to  detect   possibly   inconsistent
+            constraints.
+
+    === SUCCESS CODE ===
+       2    relative step is no more than EpsX.
+       5    MaxIts steps was taken
+       7    stopping conditions are too stringent,
+            further improvement is impossible,
+            X contains best point found so far.
+       8    user requested algorithm termination via minnlcrequesttermination(),
+            last accepted point is returned
+
+    Other fields of this structure are not documented and should not be used!
+    *************************************************************************/
+    public class minnlcreport : alglibobject
+    {
+        //
+        // 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 double bcerr { get { return _innerobj.bcerr; } set { _innerobj.bcerr = value; } }
+        public int bcidx { get { return _innerobj.bcidx; } set { _innerobj.bcidx = value; } }
+        public double lcerr { get { return _innerobj.lcerr; } set { _innerobj.lcerr = value; } }
+        public int lcidx { get { return _innerobj.lcidx; } set { _innerobj.lcidx = value; } }
+        public double nlcerr { get { return _innerobj.nlcerr; } set { _innerobj.nlcerr = value; } }
+        public int nlcidx { get { return _innerobj.nlcidx; } set { _innerobj.nlcidx = value; } }
+        public int dbgphase0its { get { return _innerobj.dbgphase0its; } set { _innerobj.dbgphase0its = value; } }
+    
+        public minnlcreport()
+        {
+            _innerobj = new minnlc.minnlcreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minnlcreport((minnlc.minnlcreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minnlc.minnlcreport _innerobj;
+        public minnlc.minnlcreport innerobj { get { return _innerobj; } }
+        public minnlcreport(minnlc.minnlcreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+    
+    /*************************************************************************
+                      NONLINEARLY  CONSTRAINED  OPTIMIZATION
+                WITH PRECONDITIONED AUGMENTED LAGRANGIAN ALGORITHM
+
+    DESCRIPTION:
+    The  subroutine  minimizes  function   F(x)  of N arguments subject to any
+    combination of:
+    * bound constraints
+    * linear inequality constraints
+    * linear equality constraints
+    * nonlinear equality constraints Gi(x)=0
+    * nonlinear inequality constraints Hi(x)<=0
+
+    REQUIREMENTS:
+    * user must provide function value and gradient for F(), H(), G()
+    * starting point X0 must be feasible or not too far away from the feasible
+      set
+    * F(), G(), H() are twice continuously differentiable on the feasible  set
+      and its neighborhood
+    * nonlinear constraints G() and H() must have non-zero gradient at  G(x)=0
+      and at H(x)=0. Say, constraint like x^2>=1 is supported, but x^2>=0   is
+      NOT supported.
+
+    USAGE:
+
+    Constrained optimization if far more complex than the  unconstrained  one.
+    Nonlinearly constrained optimization is one of the most esoteric numerical
+    procedures.
+
+    Here we give very brief outline  of  the  MinNLC  optimizer.  We  strongly
+    recommend you to study 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 MinNLCCreate() call  and  chooses
+       what NLC solver to use. There is some solver which is used by  default,
+       with default settings, but you should NOT rely on  default  choice.  It
+       may change in future releases of ALGLIB without notice, and no one  can
+       guarantee that new solver will be  able  to  solve  your  problem  with
+       default settings.
+
+       From the other side, if you choose solver explicitly, you can be pretty
+       sure that it will work with new ALGLIB releases.
+
+       In the current release following solvers can be used:
+       * SLP solver (activated with minnlcsetalgoslp() function) -  successive
+         linear programming, recommended as the first step.
+       * AUL solver (activated with minnlcsetalgoaul() function)  -  augmented
+         Lagrangian method with dense preconditioner.
+
+       SLP solver is the most robust one  in  ALGLIB  and  converges  in  less
+       iterations than AUL; however, each iteration has higher overhead  -  we
+       have to solve an LP problem. From  the  other  side,  AUL  has  cheaper
+       iterations - although it typically needs more of them, and also  it  is
+       less robust in nonconvex setting.
+
+    2. [optional] user activates OptGuard  integrity checker  which  tries  to
+       detect possible errors in the user-supplied callbacks:
+       * discontinuity/nonsmoothness of the target/nonlinear constraints
+       * errors in the analytic gradient provided by user
+       This feature is essential for early prototyping stages because it helps
+       to catch common coding and problem statement errors.
+       OptGuard can be activated with following functions (one per each  check
+       performed):
+       * minnlcoptguardsmoothness()
+       * minnlcoptguardgradient()
+
+    3. User adds boundary and/or linear and/or nonlinear constraints by  means
+       of calling one of the following functions:
+       a) minnlcsetbc() for boundary constraints
+       b) minnlcsetlc() for linear constraints
+       c) minnlcsetnlc() for nonlinear constraints
+       You may combine (a), (b) and (c) in one optimization problem.
+
+    4. User sets scale of the variables with minnlcsetscale() function. It  is
+       VERY important to set  scale  of  the  variables,  because  nonlinearly
+       constrained problems are hard to solve when variables are badly scaled.
+
+    5. User sets  stopping  conditions  with  minnlcsetcond(). If  NLC  solver
+       uses  inner/outer  iteration  layout,  this  function   sets   stopping
+       conditions for INNER iterations.
+
+    6. Finally, user calls minnlcoptimize()  function  which  takes  algorithm
+       state and pointer (delegate, etc.) to callback function which calculates
+       F/G/H.
+
+    7. User calls  minnlcresults()  to  get  solution;  additionally  you  can
+       retrieve OptGuard report with minnlcoptguardresults(), and get detailed
+       report about purported errors in the target function with:
+       * minnlcoptguardnonc1test0results()
+       * minnlcoptguardnonc1test1results()
+
+    8. Optionally user may call minnlcrestartfrom() to solve  another  problem
+       with same N but another starting point. minnlcrestartfrom()  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 06.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlccreate(int n, double[] x, out minnlcstate state)
+    {
+        state = new minnlcstate();
+        minnlc.minnlccreate(n, x, state.innerobj, null);
+    }
+    
+    public static void minnlccreate(int n, double[] x, out minnlcstate state, alglib.xparams _params)
+    {
+        state = new minnlcstate();
+        minnlc.minnlccreate(n, x, state.innerobj, _params);
+    }
+            
+    public static void minnlccreate(double[] x, out minnlcstate state)
+    {
+        int n;
+    
+        state = new minnlcstate();
+        n = ap.len(x);
+        minnlc.minnlccreate(n, x, state.innerobj, null);
+    
+        return;
+    }
+            
+    public static void minnlccreate(double[] x, out minnlcstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minnlcstate();
+        n = ap.len(x);
+        minnlc.minnlccreate(n, x, state.innerobj, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This subroutine is a finite  difference variant of MinNLCCreate(). 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 MinNLCCreate() in order to get more
+    information about creation of NLC 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 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.
+        DiffStep-   differentiation step, >0
+
+    OUTPUT PARAMETERS:
+        State   -   structure 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 MinNLCSetScale() 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-4  can  be  good
+       value to start from.
+    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.  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 06.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlccreatef(int n, double[] x, double diffstep, out minnlcstate state)
+    {
+        state = new minnlcstate();
+        minnlc.minnlccreatef(n, x, diffstep, state.innerobj, null);
+    }
+    
+    public static void minnlccreatef(int n, double[] x, double diffstep, out minnlcstate state, alglib.xparams _params)
+    {
+        state = new minnlcstate();
+        minnlc.minnlccreatef(n, x, diffstep, state.innerobj, _params);
+    }
+            
+    public static void minnlccreatef(double[] x, double diffstep, out minnlcstate state)
+    {
+        int n;
+    
+        state = new minnlcstate();
+        n = ap.len(x);
+        minnlc.minnlccreatef(n, x, diffstep, state.innerobj, null);
+    
+        return;
+    }
+            
+    public static void minnlccreatef(double[] x, double diffstep, out minnlcstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minnlcstate();
+        n = ap.len(x);
+        minnlc.minnlccreatef(n, x, diffstep, state.innerobj, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function sets boundary constraints for NLC optimizer.
+
+    Boundary constraints are inactive by  default  (after  initial  creation).
+    They are preserved after algorithm restart with  MinNLCRestartFrom().
+
+    You may combine boundary constraints with  general  linear ones - and with
+    nonlinear ones! Boundary constraints are  handled  more  efficiently  than
+    other types.  Thus,  if  your  problem  has  mixed  constraints,  you  may
+    explicitly specify some of them as boundary and save some time/space.
+
+    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:  when you solve your problem  with  augmented  Lagrangian  solver,
+             boundary constraints are  satisfied  only  approximately!  It  is
+             possible   that  algorithm  will  evaluate  function  outside  of
+             feasible area!
+
+      -- ALGLIB --
+         Copyright 06.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetbc(minnlcstate state, double[] bndl, double[] bndu)
+    {
+    
+        minnlc.minnlcsetbc(state.innerobj, bndl, bndu, null);
+    }
+    
+    public static void minnlcsetbc(minnlcstate state, double[] bndl, double[] bndu, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetbc(state.innerobj, bndl, bndu, _params);
+    }
+    
+    /*************************************************************************
+    This function sets linear constraints for MinNLC optimizer.
+
+    Linear constraints are inactive by default (after initial creation).  They
+    are preserved after algorithm restart with MinNLCRestartFrom().
+
+    You may combine linear constraints with boundary ones - and with nonlinear
+    ones! If your problem has mixed constraints, you  may  explicitly  specify
+    some of them as linear. It  may  help  optimizer   to   handle  them  more
+    efficiently.
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with MinNLCCreate 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: when you solve your problem  with  augmented  Lagrangian   solver,
+            linear constraints are  satisfied  only   approximately!   It   is
+            possible   that  algorithm  will  evaluate  function  outside   of
+            feasible area!
+
+      -- ALGLIB --
+         Copyright 06.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetlc(minnlcstate state, double[,] c, int[] ct, int k)
+    {
+    
+        minnlc.minnlcsetlc(state.innerobj, c, ct, k, null);
+    }
+    
+    public static void minnlcsetlc(minnlcstate state, double[,] c, int[] ct, int k, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetlc(state.innerobj, c, ct, k, _params);
+    }
+            
+    public static void minnlcsetlc(minnlcstate state, double[,] c, int[] ct)
+    {
+        int k;
+        if( (ap.rows(c)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'minnlcsetlc': looks like one of arguments has wrong size");
+    
+        k = ap.rows(c);
+        minnlc.minnlcsetlc(state.innerobj, c, ct, k, null);
+    
+        return;
+    }
+            
+    public static void minnlcsetlc(minnlcstate state, double[,] c, int[] ct, alglib.xparams _params)
+    {
+        int k;
+        if( (ap.rows(c)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'minnlcsetlc': looks like one of arguments has wrong size");
+    
+        k = ap.rows(c);
+        minnlc.minnlcsetlc(state.innerobj, c, ct, k, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function sets nonlinear constraints for MinNLC optimizer.
+
+    In fact, this function sets NUMBER of nonlinear  constraints.  Constraints
+    itself (constraint functions) are passed to MinNLCOptimize() method.  This
+    method requires user-defined vector function F[]  and  its  Jacobian  J[],
+    where:
+    * first component of F[] and first row  of  Jacobian  J[]  corresponds  to
+      function being minimized
+    * next NLEC components of F[] (and rows  of  J)  correspond  to  nonlinear
+      equality constraints G_i(x)=0
+    * next NLIC components of F[] (and rows  of  J)  correspond  to  nonlinear
+      inequality constraints H_i(x)<=0
+
+    NOTE: you may combine nonlinear constraints with linear/boundary ones.  If
+          your problem has mixed constraints, you  may explicitly specify some
+          of them as linear ones. It may help optimizer to  handle  them  more
+          efficiently.
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with MinNLCCreate call.
+        NLEC    -   number of Non-Linear Equality Constraints (NLEC), >=0
+        NLIC    -   number of Non-Linear Inquality Constraints (NLIC), >=0
+
+    NOTE 1: when you solve your problem  with  augmented  Lagrangian   solver,
+            nonlinear constraints are satisfied only  approximately!   It   is
+            possible   that  algorithm  will  evaluate  function  outside   of
+            feasible area!
+
+    NOTE 2: algorithm scales variables  according  to   scale   specified   by
+            MinNLCSetScale()  function,  so  it can handle problems with badly
+            scaled variables (as long as we KNOW their scales).
+
+            However,  there  is  no  way  to  automatically  scale   nonlinear
+            constraints Gi(x) and Hi(x). Inappropriate scaling  of  Gi/Hi  may
+            ruin convergence. Solving problem with  constraint  "1000*G0(x)=0"
+            is NOT same as solving it with constraint "0.001*G0(x)=0".
+
+            It  means  that  YOU  are  the  one who is responsible for correct
+            scaling of nonlinear constraints Gi(x) and Hi(x). We recommend you
+            to scale nonlinear constraints in such way that I-th component  of
+            dG/dX (or dH/dx) has approximately unit  magnitude  (for  problems
+            with unit scale)  or  has  magnitude approximately equal to 1/S[i]
+            (where S is a scale set by MinNLCSetScale() function).
+
+
+      -- ALGLIB --
+         Copyright 06.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetnlc(minnlcstate state, int nlec, int nlic)
+    {
+    
+        minnlc.minnlcsetnlc(state.innerobj, nlec, nlic, null);
+    }
+    
+    public static void minnlcsetnlc(minnlcstate state, int nlec, int nlic, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetnlc(state.innerobj, nlec, nlic, _params);
+    }
+    
+    /*************************************************************************
+    This function sets stopping conditions for inner iterations of  optimizer.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        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 - step vector, dx=X(k+1)-X(k)
+                    * s - scaling coefficients set by MinNLCSetScale()
+        MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                    iterations is unlimited.
+
+    Passing EpsX=0 and MaxIts=0 (simultaneously) will lead to automatic
+    selection of the stopping condition.
+
+      -- ALGLIB --
+         Copyright 06.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetcond(minnlcstate state, double epsx, int maxits)
+    {
+    
+        minnlc.minnlcsetcond(state.innerobj, epsx, maxits, null);
+    }
+    
+    public static void minnlcsetcond(minnlcstate state, double epsx, int maxits, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetcond(state.innerobj, epsx, maxits, _params);
+    }
+    
+    /*************************************************************************
+    This function sets scaling coefficients for NLC 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].
+
+    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 06.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetscale(minnlcstate state, double[] s)
+    {
+    
+        minnlc.minnlcsetscale(state.innerobj, s, null);
+    }
+    
+    public static void minnlcsetscale(minnlcstate state, double[] s, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetscale(state.innerobj, s, _params);
+    }
+    
+    /*************************************************************************
+    This function sets preconditioner to "inexact LBFGS-based" mode.
+
+    Preconditioning is very important for convergence of  Augmented Lagrangian
+    algorithm because presence of penalty term makes problem  ill-conditioned.
+    Difference between  performance  of  preconditioned  and  unpreconditioned
+    methods can be as large as 100x!
+
+    MinNLC optimizer may use following preconditioners,  each  with   its  own
+    benefits and drawbacks:
+        a) inexact LBFGS-based, with O(N*K) evaluation time
+        b) exact low rank one,  with O(N*K^2) evaluation time
+        c) exact robust one,    with O(N^3+K*N^2) evaluation time
+    where K is a total number of general linear and nonlinear constraints (box
+    ones are not counted).
+
+    Inexact  LBFGS-based  preconditioner  uses L-BFGS  formula  combined  with
+    orthogonality assumption to perform very fast updates. For a N-dimensional
+    problem with K general linear or nonlinear constraints (boundary ones  are
+    not counted) it has O(N*K) cost per iteration.  This   preconditioner  has
+    best  quality  (less  iterations)  when   general   linear  and  nonlinear
+    constraints are orthogonal to each other (orthogonality  with  respect  to
+    boundary constraints is not required). Number of iterations increases when
+    constraints  are  non-orthogonal, because algorithm assumes orthogonality,
+    but still it is better than no preconditioner at all.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+
+      -- ALGLIB --
+         Copyright 26.09.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetprecinexact(minnlcstate state)
+    {
+    
+        minnlc.minnlcsetprecinexact(state.innerobj, null);
+    }
+    
+    public static void minnlcsetprecinexact(minnlcstate state, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetprecinexact(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function sets preconditioner to "exact low rank" mode.
+
+    Preconditioning is very important for convergence of  Augmented Lagrangian
+    algorithm because presence of penalty term makes problem  ill-conditioned.
+    Difference between  performance  of  preconditioned  and  unpreconditioned
+    methods can be as large as 100x!
+
+    MinNLC optimizer may use following preconditioners,  each  with   its  own
+    benefits and drawbacks:
+        a) inexact LBFGS-based, with O(N*K) evaluation time
+        b) exact low rank one,  with O(N*K^2) evaluation time
+        c) exact robust one,    with O(N^3+K*N^2) evaluation time
+    where K is a total number of general linear and nonlinear constraints (box
+    ones are not counted).
+
+    It also provides special unpreconditioned mode of operation which  can  be
+    used for test purposes. Comments below discuss low rank preconditioner.
+
+    Exact low-rank preconditioner  uses  Woodbury  matrix  identity  to  build
+    quadratic model of the penalized function. It has following features:
+    * no special assumptions about orthogonality of constraints
+    * preconditioner evaluation is optimized for K<<N. Its cost  is  O(N*K^2),
+      so it may become prohibitively slow for K>=N.
+    * finally, stability of the process is guaranteed only for K<<N.  Woodbury
+      update often fail for K>=N due to degeneracy of  intermediate  matrices.
+      That's why we recommend to use "exact robust"  preconditioner  for  such
+      cases.
+
+    RECOMMENDATIONS
+
+    We  recommend  to  choose  between  "exact  low  rank"  and "exact robust"
+    preconditioners, with "low rank" version being chosen  when  you  know  in
+    advance that total count of non-box constraints won't exceed N, and "robust"
+    version being chosen when you need bulletproof solution.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        UpdateFreq- update frequency. Preconditioner is  rebuilt  after  every
+                    UpdateFreq iterations. Recommended value: 10 or higher.
+                    Zero value means that good default value will be used.
+
+      -- ALGLIB --
+         Copyright 26.09.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetprecexactlowrank(minnlcstate state, int updatefreq)
+    {
+    
+        minnlc.minnlcsetprecexactlowrank(state.innerobj, updatefreq, null);
+    }
+    
+    public static void minnlcsetprecexactlowrank(minnlcstate state, int updatefreq, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetprecexactlowrank(state.innerobj, updatefreq, _params);
+    }
+    
+    /*************************************************************************
+    This function sets preconditioner to "exact robust" mode.
+
+    Preconditioning is very important for convergence of  Augmented Lagrangian
+    algorithm because presence of penalty term makes problem  ill-conditioned.
+    Difference between  performance  of  preconditioned  and  unpreconditioned
+    methods can be as large as 100x!
+
+    MinNLC optimizer may use following preconditioners,  each  with   its  own
+    benefits and drawbacks:
+        a) inexact LBFGS-based, with O(N*K) evaluation time
+        b) exact low rank one,  with O(N*K^2) evaluation time
+        c) exact robust one,    with O(N^3+K*N^2) evaluation time
+    where K is a total number of general linear and nonlinear constraints (box
+    ones are not counted).
+
+    It also provides special unpreconditioned mode of operation which  can  be
+    used for test purposes. Comments below discuss robust preconditioner.
+
+    Exact  robust  preconditioner   uses   Cholesky  decomposition  to  invert
+    approximate Hessian matrix H=D+W'*C*W (where D stands for  diagonal  terms
+    of Hessian, combined result of initial scaling matrix and penalty from box
+    constraints; W stands for general linear constraints and linearization  of
+    nonlinear ones; C stands for diagonal matrix of penalty coefficients).
+
+    This preconditioner has following features:
+    * no special assumptions about constraint structure
+    * preconditioner is optimized  for  stability;  unlike  "exact  low  rank"
+      version which fails for K>=N, this one works well for any value of K.
+    * the only drawback is that is takes O(N^3+K*N^2) time  to  build  it.  No
+      economical  Woodbury update is applied even when it  makes  sense,  thus
+      there  are  exist situations (K<<N) when "exact low rank" preconditioner
+      outperforms this one.
+
+    RECOMMENDATIONS
+
+    We  recommend  to  choose  between  "exact  low  rank"  and "exact robust"
+    preconditioners, with "low rank" version being chosen  when  you  know  in
+    advance that total count of non-box constraints won't exceed N, and "robust"
+    version being chosen when you need bulletproof solution.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        UpdateFreq- update frequency. Preconditioner is  rebuilt  after  every
+                    UpdateFreq iterations. Recommended value: 10 or higher.
+                    Zero value means that good default value will be used.
+
+      -- ALGLIB --
+         Copyright 26.09.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetprecexactrobust(minnlcstate state, int updatefreq)
+    {
+    
+        minnlc.minnlcsetprecexactrobust(state.innerobj, updatefreq, null);
+    }
+    
+    public static void minnlcsetprecexactrobust(minnlcstate state, int updatefreq, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetprecexactrobust(state.innerobj, updatefreq, _params);
+    }
+    
+    /*************************************************************************
+    This function sets preconditioner to "turned off" mode.
+
+    Preconditioning is very important for convergence of  Augmented Lagrangian
+    algorithm because presence of penalty term makes problem  ill-conditioned.
+    Difference between  performance  of  preconditioned  and  unpreconditioned
+    methods can be as large as 100x!
+
+    MinNLC optimizer may  utilize  two  preconditioners,  each  with  its  own
+    benefits and drawbacks: a) inexact LBFGS-based, and b) exact low rank one.
+    It also provides special unpreconditioned mode of operation which  can  be
+    used for test purposes.
+
+    This function activates this test mode. Do not use it in  production  code
+    to solve real-life problems.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+
+      -- ALGLIB --
+         Copyright 26.09.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetprecnone(minnlcstate state)
+    {
+    
+        minnlc.minnlcsetprecnone(state.innerobj, null);
+    }
+    
+    public static void minnlcsetprecnone(minnlcstate state, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetprecnone(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function sets maximum step length (after scaling of step vector  with
+    respect to variable scales specified by minnlcsetscale() call).
+
+    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.
+
+    NOTE: different solvers employed by MinNLC optimizer use  different  norms
+          for step; AUL solver uses 2-norm, whilst SLP solver uses INF-norm.
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetstpmax(minnlcstate state, double stpmax)
+    {
+    
+        minnlc.minnlcsetstpmax(state.innerobj, stpmax, null);
+    }
+    
+    public static void minnlcsetstpmax(minnlcstate state, double stpmax, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetstpmax(state.innerobj, stpmax, _params);
+    }
+    
+    /*************************************************************************
+    This  function  tells MinNLC unit to use  Augmented  Lagrangian  algorithm
+    for nonlinearly constrained  optimization.  This  algorithm  is  a  slight
+    modification of one described in "A Modified Barrier-Augmented  Lagrangian
+    Method for  Constrained  Minimization  (1999)"  by  D.GOLDFARB,  R.POLYAK,
+    K. SCHEINBERG, I.YUZEFOVICH.
+
+    AUL solver can be significantly faster than SLP on easy problems, although
+    it is less robust than SLP (the "gold standard" of robust optimization).
+
+    Augmented Lagrangian algorithm works by converting problem  of  minimizing
+    F(x) subject to equality/inequality constraints   to unconstrained problem
+    of the form
+
+        min[ f(x) +
+            + Rho*PENALTY_EQ(x)   + SHIFT_EQ(x,Nu1) +
+            + Rho*PENALTY_INEQ(x) + SHIFT_INEQ(x,Nu2) ]
+
+    where:
+    * Rho is a fixed penalization coefficient
+    * PENALTY_EQ(x) is a penalty term, which is used to APPROXIMATELY  enforce
+      equality constraints
+    * SHIFT_EQ(x) is a special "shift"  term  which  is  used  to  "fine-tune"
+      equality constraints, greatly increasing precision
+    * PENALTY_INEQ(x) is a penalty term which is used to approximately enforce
+      inequality constraints
+    * SHIFT_INEQ(x) is a special "shift"  term  which  is  used to "fine-tune"
+      inequality constraints, greatly increasing precision
+    * Nu1/Nu2 are vectors of Lagrange coefficients which are fine-tuned during
+      outer iterations of algorithm
+
+    This  version  of  AUL  algorithm  uses   preconditioner,  which   greatly
+    accelerates convergence. Because this  algorithm  is  similar  to  penalty
+    methods,  it  may  perform  steps  into  infeasible  area.  All  kinds  of
+    constraints (boundary, linear and nonlinear ones) may   be   violated   in
+    intermediate points - and in the solution.  However,  properly  configured
+    AUL method is significantly better at handling  constraints  than  barrier
+    and/or penalty methods.
+
+    The very basic outline of algorithm is given below:
+    1) first outer iteration is performed with "default"  values  of  Lagrange
+       multipliers Nu1/Nu2. Solution quality is low (candidate  point  can  be
+       too  far  away  from  true  solution; large violation of constraints is
+       possible) and is comparable with that of penalty methods.
+    2) subsequent outer iterations  refine  Lagrange  multipliers  and improve
+       quality of the solution.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        Rho     -   penalty coefficient, Rho>0:
+                    * large enough  that  algorithm  converges  with   desired
+                      precision. Minimum value is 10*max(S'*diag(H)*S),  where
+                      S is a scale matrix (set by MinNLCSetScale) and H  is  a
+                      Hessian of the function being minimized. If you can  not
+                      easily estimate Hessian norm,  see  our  recommendations
+                      below.
+                    * not TOO large to prevent ill-conditioning
+                    * for unit-scale problems (variables and Hessian have unit
+                      magnitude), Rho=100 or Rho=1000 can be used.
+                    * it is important to note that Rho is internally multiplied
+                      by scaling matrix, i.e. optimum value of Rho depends  on
+                      scale of variables specified  by  MinNLCSetScale().
+        ItsCnt  -   number of outer iterations:
+                    * ItsCnt=0 means that small number of outer iterations  is
+                      automatically chosen (10 iterations in current version).
+                    * ItsCnt=1 means that AUL algorithm performs just as usual
+                      barrier method.
+                    * ItsCnt>1 means that  AUL  algorithm  performs  specified
+                      number of outer iterations
+
+    HOW TO CHOOSE PARAMETERS
+
+    Nonlinear optimization is a tricky area and Augmented Lagrangian algorithm
+    is sometimes hard to tune. Good values of  Rho  and  ItsCnt  are  problem-
+    specific.  In  order  to  help  you   we   prepared   following   set   of
+    recommendations:
+
+    * for  unit-scale  problems  (variables  and Hessian have unit magnitude),
+      Rho=100 or Rho=1000 can be used.
+
+    * start from  some  small  value of Rho and solve problem  with  just  one
+      outer iteration (ItcCnt=1). In this case algorithm behaves like  penalty
+      method. Increase Rho in 2x or 10x steps until you  see  that  one  outer
+      iteration returns point which is "rough approximation to solution".
+
+      It is very important to have Rho so  large  that  penalty  term  becomes
+      constraining i.e. modified function becomes highly convex in constrained
+      directions.
+
+      From the other side, too large Rho may prevent you  from  converging  to
+      the solution. You can diagnose it by studying number of inner iterations
+      performed by algorithm: too few (5-10 on  1000-dimensional  problem)  or
+      too many (orders of magnitude more than  dimensionality)  usually  means
+      that Rho is too large.
+
+    * with just one outer iteration you  usually  have  low-quality  solution.
+      Some constraints can be violated with very  large  margin,  while  other
+      ones (which are NOT violated in the true solution) can push final  point
+      too far in the inner area of the feasible set.
+
+      For example, if you have constraint x0>=0 and true solution  x0=1,  then
+      merely a presence of "x0>=0" will introduce a bias towards larger values
+      of x0. Say, algorithm may stop at x0=1.5 instead of 1.0.
+
+    * after you found good Rho, you may increase number of  outer  iterations.
+      ItsCnt=10 is a good value. Subsequent outer iteration will refine values
+      of  Lagrange  multipliers.  Constraints  which  were  violated  will  be
+      enforced, inactive constraints will be dropped (corresponding multipliers
+      will be decreased). Ideally, you  should  see  10-1000x  improvement  in
+      constraint handling (constraint violation is reduced).
+
+    * if  you  see  that  algorithm  converges  to  vicinity  of solution, but
+      additional outer iterations do not refine solution,  it  may  mean  that
+      algorithm is unstable - it wanders around true  solution,  but  can  not
+      approach it. Sometimes algorithm may be stabilized by increasing Rho one
+      more time, making it 5x or 10x larger.
+
+    SCALING OF CONSTRAINTS [IMPORTANT]
+
+    AUL optimizer scales   variables   according   to   scale   specified   by
+    MinNLCSetScale() function, so it can handle  problems  with  badly  scaled
+    variables (as long as we KNOW their scales).   However,  because  function
+    being optimized is a mix  of  original  function and  constraint-dependent
+    penalty  functions, it  is   important  to   rescale  both  variables  AND
+    constraints.
+
+    Say,  if  you  minimize f(x)=x^2 subject to 1000000*x>=0,  then  you  have
+    constraint whose scale is different from that of target  function (another
+    example is 0.000001*x>=0). It is also possible to have constraints   whose
+    scales  are   misaligned:   1000000*x0>=0, 0.000001*x1<=0.   Inappropriate
+    scaling may ruin convergence because minimizing x^2 subject to x>=0 is NOT
+    same as minimizing it subject to 1000000*x>=0.
+
+    Because we  know  coefficients  of  boundary/linear  constraints,  we  can
+    automatically rescale and normalize them. However,  there  is  no  way  to
+    automatically rescale nonlinear constraints Gi(x) and  Hi(x)  -  they  are
+    black boxes.
+
+    It means that YOU are the one who is  responsible  for  correct scaling of
+    nonlinear constraints  Gi(x)  and  Hi(x).  We  recommend  you  to  rescale
+    nonlinear constraints in such way that I-th component of dG/dX (or  dH/dx)
+    has magnitude approximately equal to 1/S[i] (where S  is  a  scale  set by
+    MinNLCSetScale() function).
+
+    WHAT IF IT DOES NOT CONVERGE?
+
+    It is possible that AUL algorithm fails to converge to precise  values  of
+    Lagrange multipliers. It stops somewhere around true solution, but candidate
+    point is still too far from solution, and some constraints  are  violated.
+    Such kind of failure is specific for Lagrangian algorithms -  technically,
+    they stop at some point, but this point is not constrained solution.
+
+    There are exist several reasons why algorithm may fail to converge:
+    a) too loose stopping criteria for inner iteration
+    b) degenerate, redundant constraints
+    c) target function has unconstrained extremum exactly at the  boundary  of
+       some constraint
+    d) numerical noise in the target function
+
+    In all these cases algorithm is unstable - each outer iteration results in
+    large and almost random step which improves handling of some  constraints,
+    but violates other ones (ideally  outer iterations should form a  sequence
+    of progressively decreasing steps towards solution).
+
+    First reason possible is  that  too  loose  stopping  criteria  for  inner
+    iteration were specified. Augmented Lagrangian algorithm solves a sequence
+    of intermediate problems, and requries each of them to be solved with high
+    precision. Insufficient precision results in incorrect update of  Lagrange
+    multipliers.
+
+    Another reason is that you may have specified degenerate constraints: say,
+    some constraint was repeated twice. In most cases AUL algorithm gracefully
+    handles such situations, but sometimes it may spend too much time figuring
+    out subtle degeneracies in constraint matrix.
+
+    Third reason is tricky and hard to diagnose. Consider situation  when  you
+    minimize  f=x^2  subject to constraint x>=0.  Unconstrained   extremum  is
+    located  exactly  at  the  boundary  of  constrained  area.  In  this case
+    algorithm will tend to oscillate between negative  and  positive  x.  Each
+    time it stops at x<0 it "reinforces" constraint x>=0, and each time it  is
+    bounced to x>0 it "relaxes" constraint (and is  attracted  to  x<0).
+
+    Such situation  sometimes  happens  in  problems  with  hidden  symetries.
+    Algorithm  is  got  caught  in  a  loop with  Lagrange  multipliers  being
+    continuously increased/decreased. Luckily, such loop forms after at  least
+    three iterations, so this problem can be solved by  DECREASING  number  of
+    outer iterations down to 1-2 and increasing  penalty  coefficient  Rho  as
+    much as possible.
+
+    Final reason is numerical noise. AUL algorithm is robust against  moderate
+    noise (more robust than, say, active set methods),  but  large  noise  may
+    destabilize algorithm.
+
+      -- ALGLIB --
+         Copyright 06.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetalgoaul(minnlcstate state, double rho, int itscnt)
+    {
+    
+        minnlc.minnlcsetalgoaul(state.innerobj, rho, itscnt, null);
+    }
+    
+    public static void minnlcsetalgoaul(minnlcstate state, double rho, int itscnt, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetalgoaul(state.innerobj, rho, itscnt, _params);
+    }
+    
+    /*************************************************************************
+    This   function  tells  MinNLC  optimizer  to  use  SLP (Successive Linear
+    Programming) algorithm for  nonlinearly  constrained   optimization.  This
+    algorithm  is  a  slight  modification  of  one  described  in  "A  Linear
+    programming-based optimization algorithm for solving nonlinear programming
+    problems" (2010) by Claus Still and Tapio Westerlund.
+
+    Despite its name ("linear" = "first order method") this algorithm performs
+    steps similar to that of conjugate gradients method;  internally  it  uses
+    orthogonality/conjugacy requirement for subsequent steps  which  makes  it
+    closer to second order methods in terms of convergence speed.
+
+    Convergence is proved for the following case:
+    * function and constraints are continuously differentiable (C1 class)
+    * extended MangasarianâFromovitz constraint qualification  (EMFCQ)  holds;
+      in the context of this algorithm EMFCQ  means  that  one  can,  for  any
+      infeasible  point,  find  a  search  direction  such that the constraint
+      infeasibilities are reduced.
+
+    This algorithm has following nice properties:
+    * no parameters to tune
+    * no convexity requirements for target function or constraints
+    * initial point can be infeasible
+    * algorithm respects box constraints in all intermediate points  (it  does
+      not even evaluate function outside of box constrained area)
+    * once linear constraints are enforced, algorithm will not violate them
+    * no such guarantees can be provided for nonlinear constraints,  but  once
+      nonlinear constraints are enforced, algorithm will try  to  respect them
+      as much as possible
+    * numerical differentiation does not  violate  box  constraints  (although
+      general linear and nonlinear ones can be violated during differentiation)
+
+    However, following drawbacks can be noted:
+    * algorithm performance decreased on problems with dense constraints
+    * it has higher iteration cost than AUL - we have to solve an  LP  problem
+      at each step.
+
+    We recommend this algorithm as a first step; as soon as you make sure that
+    it converges, you can try switching to AUL which is sometimes much faster.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 02.04.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetalgoslp(minnlcstate state)
+    {
+    
+        minnlc.minnlcsetalgoslp(state.innerobj, null);
+    }
+    
+    public static void minnlcsetalgoslp(minnlcstate state, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetalgoslp(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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 MinNLCOptimize().
+
+    NOTE: algorithm passes two parameters to rep() callback  -  current  point
+          and penalized function value at current point. Important -  function
+          value which is returned is NOT function being minimized. It  is  sum
+          of the value of the function being minimized - and penalty term.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcsetxrep(minnlcstate state, bool needxrep)
+    {
+    
+        minnlc.minnlcsetxrep(state.innerobj, needxrep, null);
+    }
+    
+    public static void minnlcsetxrep(minnlcstate state, bool needxrep, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcsetxrep(state.innerobj, needxrep, _params);
+    }
+    
+    /*************************************************************************
+    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 minnlciteration(minnlcstate state)
+    {
+    
+        return minnlc.minnlciteration(state.innerobj, null);
+    }
+    
+    public static bool minnlciteration(minnlcstate state, alglib.xparams _params)
+    {
+    
+        return minnlc.minnlciteration(state.innerobj, _params);
+    }
+    /*************************************************************************
+    This family of functions is used to launcn iterations of nonlinear optimizer
+
+    These functions accept following parameters:
+        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. This function has two different implementations: one which  uses  exact
+       (analytical) user-supplied Jacobian, and one which uses  only  function
+       vector and numerically  differentiates  function  in  order  to  obtain
+       gradient.
+
+       Depending  on  the  specific  function  used to create optimizer object
+       you should choose appropriate variant of MinNLCOptimize() -  one  which
+       accepts function AND Jacobian or one which accepts ONLY function.
+
+       Be careful to choose variant of MinNLCOptimize()  which  corresponds to
+       your optimization scheme! Table below lists different  combinations  of
+       callback (function/gradient) passed to MinNLCOptimize()   and  specific
+       function used to create optimizer.
+
+
+                         |         USER PASSED TO MinNLCOptimize()
+       CREATED WITH      |  function only   |  function and gradient
+       ------------------------------------------------------------
+       MinNLCCreateF()   |     works               FAILS
+       MinNLCCreate()    |     FAILS               works
+
+       Here "FAILS" denotes inappropriate combinations  of  optimizer creation
+       function  and  MinNLCOptimize()  version.   Attemps   to    use    such
+       combination will lead to exception. Either  you  did  not pass gradient
+       when it WAS needed or you passed gradient when it was NOT needed.
+
+      -- ALGLIB --
+         Copyright 06.06.2014 by Bochkanov Sergey
+
+    *************************************************************************/
+    public static void minnlcoptimize(minnlcstate state, ndimensional_fvec  fvec, ndimensional_rep rep, object obj)
+    {
+        minnlcoptimize(state, fvec, rep, obj, null);
+    }
+    
+    public static void minnlcoptimize(minnlcstate state, ndimensional_fvec  fvec, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( fvec==null )
+            throw new alglibexception("ALGLIB: error in 'minnlcoptimize()' (fvec is null)");
+        while( alglib.minnlciteration(state, _params) )
+        {
+            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 'minnlcoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    public static void minnlcoptimize(minnlcstate state, ndimensional_jac  jac, ndimensional_rep rep, object obj)
+    {
+        minnlcoptimize(state, jac, rep, obj, null);
+    }
+    
+    public static void minnlcoptimize(minnlcstate state, ndimensional_jac  jac, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( jac==null )
+            throw new alglibexception("ALGLIB: error in 'minnlcoptimize()' (jac is null)");
+        while( alglib.minnlciteration(state, _params) )
+        {
+            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 'minnlcoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    
+    /*************************************************************************
+    This  function  activates/deactivates verification  of  the  user-supplied
+    analytic gradient/Jacobian.
+
+    Upon  activation  of  this  option  OptGuard  integrity  checker  performs
+    numerical differentiation of your target  function  (constraints)  at  the
+    initial point (note: future versions may also perform check  at  the final
+    point) and compares numerical gradient/Jacobian with analytic one provided
+    by you.
+
+    If difference is too large, an error flag is set and optimization  session
+    continues. After optimization session is over, you can retrieve the report
+    which stores both gradients/Jacobians, and specific components highlighted
+    as suspicious by the OptGuard.
+
+    The primary OptGuard report can be retrieved with minnlcoptguardresults().
+
+    IMPORTANT: gradient check is a high-overhead option which  will  cost  you
+               about 3*N additional function evaluations. In many cases it may
+               cost as much as the rest of the optimization session.
+
+               YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO
+               CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY.
+
+    NOTE: unlike previous incarnation of the gradient checking code,  OptGuard
+          does NOT interrupt optimization even if it discovers bad gradient.
+
+    INPUT PARAMETERS:
+        State       -   structure used to store algorithm state
+        TestStep    -   verification step used for numerical differentiation:
+                        * TestStep=0 turns verification off
+                        * TestStep>0 activates verification
+                        You should carefully choose TestStep. Value  which  is
+                        too large (so large that  function  behavior  is  non-
+                        cubic at this scale) will lead  to  false  alarms. Too
+                        short step will result in rounding  errors  dominating
+                        numerical derivative.
+
+                        You may use different step for different parameters by
+                        means of setting scale with minnlcsetscale().
+
+    === EXPLANATION ==========================================================
+
+    In order to verify gradient algorithm performs following steps:
+      * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i],
+        where X[i] is i-th component of the initial point and S[i] is a  scale
+        of i-th parameter
+      * F(X) is evaluated at these trial points
+      * we perform one more evaluation in the middle point of the interval
+      * we  build  cubic  model using function values and derivatives at trial
+        points and we compare its prediction with actual value in  the  middle
+        point
+
+      -- ALGLIB --
+         Copyright 15.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcoptguardgradient(minnlcstate state, double teststep)
+    {
+    
+        minnlc.minnlcoptguardgradient(state.innerobj, teststep, null);
+    }
+    
+    public static void minnlcoptguardgradient(minnlcstate state, double teststep, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcoptguardgradient(state.innerobj, teststep, _params);
+    }
+    
+    /*************************************************************************
+    This  function  activates/deactivates nonsmoothness monitoring  option  of
+    the  OptGuard  integrity  checker. Smoothness  monitor  silently  observes
+    solution process and tries to detect ill-posed problems, i.e. ones with:
+    a) discontinuous target function (non-C0) and/or constraints
+    b) nonsmooth     target function (non-C1) and/or constraints
+
+    Smoothness monitoring does NOT interrupt optimization  even if it suspects
+    that your problem is nonsmooth. It just sets corresponding  flags  in  the
+    OptGuard report which can be retrieved after optimization is over.
+
+    Smoothness monitoring is a moderate overhead option which often adds  less
+    than 1% to the optimizer running time. Thus, you can use it even for large
+    scale problems.
+
+    NOTE: OptGuard does  NOT  guarantee  that  it  will  always  detect  C0/C1
+          continuity violations.
+
+          First, minor errors are hard to  catch - say, a 0.0001 difference in
+          the model values at two sides of the gap may be due to discontinuity
+          of the model - or simply because the model has changed.
+
+          Second, C1-violations  are  especially  difficult  to  detect  in  a
+          noninvasive way. The optimizer usually  performs  very  short  steps
+          near the nonsmoothness, and differentiation  usually   introduces  a
+          lot of numerical noise.  It  is  hard  to  tell  whether  some  tiny
+          discontinuity in the slope is due to real nonsmoothness or just  due
+          to numerical noise alone.
+
+          Our top priority was to avoid false positives, so in some rare cases
+          minor errors may went unnoticed (however, in most cases they can  be
+          spotted with restart from different initial point).
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+        level   -   monitoring level:
+                    * 0 - monitoring is disabled
+                    * 1 - noninvasive low-overhead monitoring; function values
+                          and/or gradients are recorded, but OptGuard does not
+                          try to perform additional evaluations  in  order  to
+                          get more information about suspicious locations.
+
+    === EXPLANATION ==========================================================
+
+    One major source of headache during optimization  is  the  possibility  of
+    the coding errors in the target function/constraints (or their gradients).
+    Such  errors   most   often   manifest   themselves  as  discontinuity  or
+    nonsmoothness of the target/constraints.
+
+    Another frequent situation is when you try to optimize something involving
+    lots of min() and max() operations, i.e. nonsmooth target. Although not  a
+    coding error, it is nonsmoothness anyway - and smooth  optimizers  usually
+    stop right after encountering nonsmoothness, well before reaching solution.
+
+    OptGuard integrity checker helps you to catch such situations: it monitors
+    function values/gradients being passed  to  the  optimizer  and  tries  to
+    errors. Upon discovering suspicious pair of points it  raises  appropriate
+    flag (and allows you to continue optimization). When optimization is done,
+    you can study OptGuard result.
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcoptguardsmoothness(minnlcstate state, int level)
+    {
+    
+        minnlc.minnlcoptguardsmoothness(state.innerobj, level, null);
+    }
+    
+    public static void minnlcoptguardsmoothness(minnlcstate state, int level, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcoptguardsmoothness(state.innerobj, level, _params);
+    }
+            
+    public static void minnlcoptguardsmoothness(minnlcstate state)
+    {
+        int level;
+    
+    
+        level = 1;
+        minnlc.minnlcoptguardsmoothness(state.innerobj, level, null);
+    
+        return;
+    }
+            
+    public static void minnlcoptguardsmoothness(minnlcstate state, alglib.xparams _params)
+    {
+        int level;
+    
+    
+        level = 1;
+        minnlc.minnlcoptguardsmoothness(state.innerobj, level, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Results of OptGuard integrity check, should be called  after  optimization
+    session is over.
+
+    === PRIMARY REPORT =======================================================
+
+    OptGuard performs several checks which are intended to catch common errors
+    in the implementation of nonlinear function/gradient:
+    * incorrect analytic gradient
+    * discontinuous (non-C0) target functions (constraints)
+    * nonsmooth     (non-C1) target functions (constraints)
+
+    Each of these checks is activated with appropriate function:
+    * minnlcoptguardgradient() for gradient verification
+    * minnlcoptguardsmoothness() for C0/C1 checks
+
+    Following flags are set when these errors are suspected:
+    * rep.badgradsuspected, and additionally:
+      * rep.badgradfidx for specific function (Jacobian row) suspected
+      * rep.badgradvidx for specific variable (Jacobian column) suspected
+      * rep.badgradxbase, a point where gradient/Jacobian is tested
+      * rep.badgraduser, user-provided gradient/Jacobian
+      * rep.badgradnum, reference gradient/Jacobian obtained via numerical
+        differentiation
+    * rep.nonc0suspected, and additionally:
+      * rep.nonc0fidx - an index of specific function violating C0 continuity
+    * rep.nonc1suspected, and additionally
+      * rep.nonc1fidx - an index of specific function violating C1 continuity
+    Here function index 0 means  target function, index 1  or  higher  denotes
+    nonlinear constraints.
+
+    === ADDITIONAL REPORTS/LOGS ==============================================
+
+    Several different tests are performed to catch C0/C1 errors, you can  find
+    out specific test signaled error by looking to:
+    * rep.nonc0test0positive, for non-C0 test #0
+    * rep.nonc1test0positive, for non-C1 test #0
+    * rep.nonc1test1positive, for non-C1 test #1
+
+    Additional information (including line search logs)  can  be  obtained  by
+    means of:
+    * minnlcoptguardnonc1test0results()
+    * minnlcoptguardnonc1test1results()
+    which return detailed error reports, specific points where discontinuities
+    were found, and so on.
+
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        rep     -   generic OptGuard report;  more  detailed  reports  can  be
+                    retrieved with other functions.
+
+    NOTE: false negatives (nonsmooth problems are not identified as  nonsmooth
+          ones) are possible although unlikely.
+
+          The reason  is  that  you  need  to  make several evaluations around
+          nonsmoothness  in  order  to  accumulate  enough  information  about
+          function curvature. Say, if you start right from the nonsmooth point,
+          optimizer simply won't get enough data to understand what  is  going
+          wrong before it terminates due to abrupt changes in the  derivative.
+          It is also  possible  that  "unlucky"  step  will  move  us  to  the
+          termination too quickly.
+
+          Our current approach is to have less than 0.1%  false  negatives  in
+          our test examples  (measured  with  multiple  restarts  from  random
+          points), and to have exactly 0% false positives.
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcoptguardresults(minnlcstate state, out optguardreport rep)
+    {
+        rep = new optguardreport();
+        minnlc.minnlcoptguardresults(state.innerobj, rep.innerobj, null);
+    }
+    
+    public static void minnlcoptguardresults(minnlcstate state, out optguardreport rep, alglib.xparams _params)
+    {
+        rep = new optguardreport();
+        minnlc.minnlcoptguardresults(state.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Detailed results of the OptGuard integrity check for nonsmoothness test #0
+
+    Nonsmoothness (non-C1) test #0 studies  function  values  (not  gradient!)
+    obtained during line searches and monitors  behavior  of  the  directional
+    derivative estimate.
+
+    This test is less powerful than test #1, but it does  not  depend  on  the
+    gradient values and thus it is more robust against artifacts introduced by
+    numerical differentiation.
+
+    Two reports are returned:
+    * a "strongest" one, corresponding  to  line   search  which  had  highest
+      value of the nonsmoothness indicator
+    * a "longest" one, corresponding to line search which  had  more  function
+      evaluations, and thus is more detailed
+
+    In both cases following fields are returned:
+
+    * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+      did not notice anything (in the latter cases fields below are empty).
+    * fidx - is an index of the function (0 for  target  function, 1 or higher
+      for nonlinear constraints) which is suspected of being "non-C1"
+    * x0[], d[] - arrays of length N which store initial point  and  direction
+      for line search (d[] can be normalized, but does not have to)
+    * stp[], f[] - arrays of length CNT which store step lengths and  function
+      values at these points; f[i] is evaluated in x0+stp[i]*d.
+    * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+      between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+      with  most  likely  position  of  the  violation  between  stpidxa+1 and
+      stpidxa+2.
+
+    ==========================================================================
+    = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+    =                   see where C1 continuity is violated.
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        strrep  -   C1 test #0 "strong" report
+        lngrep  -   C1 test #0 "long" report
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcoptguardnonc1test0results(minnlcstate state, out optguardnonc1test0report strrep, out optguardnonc1test0report lngrep)
+    {
+        strrep = new optguardnonc1test0report();
+        lngrep = new optguardnonc1test0report();
+        minnlc.minnlcoptguardnonc1test0results(state.innerobj, strrep.innerobj, lngrep.innerobj, null);
+    }
+    
+    public static void minnlcoptguardnonc1test0results(minnlcstate state, out optguardnonc1test0report strrep, out optguardnonc1test0report lngrep, alglib.xparams _params)
+    {
+        strrep = new optguardnonc1test0report();
+        lngrep = new optguardnonc1test0report();
+        minnlc.minnlcoptguardnonc1test0results(state.innerobj, strrep.innerobj, lngrep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Detailed results of the OptGuard integrity check for nonsmoothness test #1
+
+    Nonsmoothness (non-C1)  test  #1  studies  individual  components  of  the
+    gradient computed during line search.
+
+    When precise analytic gradient is provided this test is more powerful than
+    test #0  which  works  with  function  values  and  ignores  user-provided
+    gradient.  However,  test  #0  becomes  more   powerful   when   numerical
+    differentiation is employed (in such cases test #1 detects  higher  levels
+    of numerical noise and becomes too conservative).
+
+    This test also tells specific components of the gradient which violate  C1
+    continuity, which makes it more informative than #0, which just tells that
+    continuity is violated.
+
+    Two reports are returned:
+    * a "strongest" one, corresponding  to  line   search  which  had  highest
+      value of the nonsmoothness indicator
+    * a "longest" one, corresponding to line search which  had  more  function
+      evaluations, and thus is more detailed
+
+    In both cases following fields are returned:
+
+    * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+      did not notice anything (in the latter cases fields below are empty).
+    * fidx - is an index of the function (0 for  target  function, 1 or higher
+      for nonlinear constraints) which is suspected of being "non-C1"
+    * vidx - is an index of the variable in [0,N) with nonsmooth derivative
+    * x0[], d[] - arrays of length N which store initial point  and  direction
+      for line search (d[] can be normalized, but does not have to)
+    * stp[], g[] - arrays of length CNT which store step lengths and  gradient
+      values at these points; g[i] is evaluated in  x0+stp[i]*d  and  contains
+      vidx-th component of the gradient.
+    * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+      between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+      with  most  likely  position  of  the  violation  between  stpidxa+1 and
+      stpidxa+2.
+
+    ==========================================================================
+    = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+    =                   see where C1 continuity is violated.
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        strrep  -   C1 test #1 "strong" report
+        lngrep  -   C1 test #1 "long" report
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcoptguardnonc1test1results(minnlcstate state, out optguardnonc1test1report strrep, out optguardnonc1test1report lngrep)
+    {
+        strrep = new optguardnonc1test1report();
+        lngrep = new optguardnonc1test1report();
+        minnlc.minnlcoptguardnonc1test1results(state.innerobj, strrep.innerobj, lngrep.innerobj, null);
+    }
+    
+    public static void minnlcoptguardnonc1test1results(minnlcstate state, out optguardnonc1test1report strrep, out optguardnonc1test1report lngrep, alglib.xparams _params)
+    {
+        strrep = new optguardnonc1test1report();
+        lngrep = new optguardnonc1test1report();
+        minnlc.minnlcoptguardnonc1test1results(state.innerobj, strrep.innerobj, lngrep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    MinNLC results:  the  solution  found,  completion  codes  and  additional
+    information.
+
+    If you activated OptGuard integrity checking functionality and want to get
+    OptGuard report, it can be retrieved with:
+    * minnlcoptguardresults() - for a primary report about (a) suspected C0/C1
+      continuity violations and (b) errors in the analytic gradient.
+    * minnlcoptguardnonc1test0results() - for C1 continuity violation test #0,
+      detailed line search log
+    * minnlcoptguardnonc1test1results() - for C1 continuity violation test #1,
+      detailed line search log
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[0..N-1], solution
+        Rep     -   optimization report, contains information about completion
+                    code, constraint violation at the solution and so on.
+
+                    You   should   check   rep.terminationtype  in  order   to
+                    distinguish successful termination from unsuccessful one:
+
+                    === FAILURE CODES ===
+                    * -8    internal  integrity control  detected  infinite or
+                            NAN   values    in   function/gradient.   Abnormal
+                            termination signalled.
+                    * -3    box  constraints are infeasible.
+                            Note: infeasibility of  non-box  constraints  does
+                                  NOT trigger emergency completion;  you  have
+                                  to examine rep.bcerr/rep.lcerr/rep.nlcerr to
+                                  detect possibly inconsistent constraints.
+
+                    === SUCCESS CODES ===
+                    *  2   scaled step is no more than EpsX.
+                    *  5   MaxIts steps were taken.
+                    *  8   user   requested    algorithm    termination    via
+                           minnlcrequesttermination(), last accepted point  is
+                           returned.
+
+                    More information about fields of this  structure  can  be
+                    found in the comments on minnlcreport datatype.
+
+      -- ALGLIB --
+         Copyright 06.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcresults(minnlcstate state, out double[] x, out minnlcreport rep)
+    {
+        x = new double[0];
+        rep = new minnlcreport();
+        minnlc.minnlcresults(state.innerobj, ref x, rep.innerobj, null);
+    }
+    
+    public static void minnlcresults(minnlcstate state, out double[] x, out minnlcreport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new minnlcreport();
+        minnlc.minnlcresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    NLC results
+
+    Buffered implementation of MinNLCResults() 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 minnlcresultsbuf(minnlcstate state, ref double[] x, minnlcreport rep)
+    {
+    
+        minnlc.minnlcresultsbuf(state.innerobj, ref x, rep.innerobj, null);
+    }
+    
+    public static void minnlcresultsbuf(minnlcstate state, ref double[] x, minnlcreport rep, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcresultsbuf(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine submits request for termination of running  optimizer.  It
+    should be called from user-supplied callback when user decides that it  is
+    time to "smoothly" terminate optimization process.  As  result,  optimizer
+    stops at point which was "current accepted" when termination  request  was
+    submitted and returns error code 8 (successful termination).
+
+    INPUT PARAMETERS:
+        State   -   optimizer structure
+
+    NOTE: after  request  for  termination  optimizer  may   perform   several
+          additional calls to user-supplied callbacks. It does  NOT  guarantee
+          to stop immediately - it just guarantees that these additional calls
+          will be discarded later.
+
+    NOTE: calling this function on optimizer which is NOT running will have no
+          effect.
+
+    NOTE: multiple calls to this function are possible. First call is counted,
+          subsequent calls are silently ignored.
+
+      -- ALGLIB --
+         Copyright 08.10.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcrequesttermination(minnlcstate state)
+    {
+    
+        minnlc.minnlcrequesttermination(state.innerobj, null);
+    }
+    
+    public static void minnlcrequesttermination(minnlcstate state, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcrequesttermination(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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 MinNLCCreate call.
+        X       -   new starting point.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnlcrestartfrom(minnlcstate state, double[] x)
+    {
+    
+        minnlc.minnlcrestartfrom(state.innerobj, x, null);
+    }
+    
+    public static void minnlcrestartfrom(minnlcstate state, double[] x, alglib.xparams _params)
+    {
+    
+        minnlc.minnlcrestartfrom(state.innerobj, x, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This object stores nonlinear optimizer state.
+    You should use functions provided by MinBC subpackage to work with this
+    object
+    *************************************************************************/
+    public class minbcstate : alglibobject
+    {
+        //
+        // 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 minbcstate()
+        {
+            _innerobj = new minbc.minbcstate();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minbcstate((minbc.minbcstate)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minbc.minbcstate _innerobj;
+        public minbc.minbcstate innerobj { get { return _innerobj; } }
+        public minbcstate(minbc.minbcstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This structure stores optimization report:
+    * iterationscount           number of iterations
+    * nfev                      number of gradient evaluations
+    * terminationtype           termination type (see below)
+
+    TERMINATION CODES
+
+    terminationtype field contains completion code, which can be:
+      -8    internal integrity control detected  infinite  or  NAN  values  in
+            function/gradient. Abnormal termination signalled.
+      -3    inconsistent constraints.
+       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,
+            X contains best point found so far.
+       8    terminated by user who called minbcrequesttermination(). X contains
+            point which was "current accepted" when  termination  request  was
+            submitted.
+    *************************************************************************/
+    public class minbcreport : alglibobject
+    {
+        //
+        // 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 varidx { get { return _innerobj.varidx; } set { _innerobj.varidx = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+    
+        public minbcreport()
+        {
+            _innerobj = new minbc.minbcreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minbcreport((minbc.minbcreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minbc.minbcreport _innerobj;
+        public minbc.minbcreport innerobj { get { return _innerobj; } }
+        public minbcreport(minbc.minbcreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+    
+    /*************************************************************************
+                         BOX CONSTRAINED OPTIMIZATION
+              WITH FAST ACTIVATION OF MULTIPLE BOX CONSTRAINTS
+
+    DESCRIPTION:
+    The  subroutine  minimizes  function   F(x) of N arguments subject  to box
+    constraints (with some of box constraints actually being equality ones).
+
+    This optimizer uses algorithm similar to that of MinBLEIC (optimizer  with
+    general linear constraints), but presence of box-only  constraints  allows
+    us to use faster constraint activation strategies. On large-scale problems,
+    with multiple constraints active at the solution, this  optimizer  can  be
+    several times faster than BLEIC.
+
+    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 BC 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 MinBCCreate() call
+
+    2. USer adds box constraints by calling MinBCSetBC() function.
+
+    3. User sets stopping conditions with MinBCSetCond().
+
+    4. User calls MinBCOptimize() function which takes algorithm  state and
+       pointer (delegate, etc.) to callback function which calculates F/G.
+
+    5. User calls MinBCResults() to get solution
+
+    6. Optionally user may call MinBCRestartFrom() to solve another problem
+       with same N but another starting point.
+       MinBCRestartFrom() 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 minbccreate(int n, double[] x, out minbcstate state)
+    {
+        state = new minbcstate();
+        minbc.minbccreate(n, x, state.innerobj, null);
+    }
+    
+    public static void minbccreate(int n, double[] x, out minbcstate state, alglib.xparams _params)
+    {
+        state = new minbcstate();
+        minbc.minbccreate(n, x, state.innerobj, _params);
+    }
+            
+    public static void minbccreate(double[] x, out minbcstate state)
+    {
+        int n;
+    
+        state = new minbcstate();
+        n = ap.len(x);
+        minbc.minbccreate(n, x, state.innerobj, null);
+    
+        return;
+    }
+            
+    public static void minbccreate(double[] x, out minbcstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minbcstate();
+        n = ap.len(x);
+        minbc.minbccreate(n, x, state.innerobj, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    The subroutine is finite difference variant of MinBCCreate().  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 MinBCCreate() in  order  to  get
+    more information about creation of BC 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 MinBCSetScale() 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 minbccreatef(int n, double[] x, double diffstep, out minbcstate state)
+    {
+        state = new minbcstate();
+        minbc.minbccreatef(n, x, diffstep, state.innerobj, null);
+    }
+    
+    public static void minbccreatef(int n, double[] x, double diffstep, out minbcstate state, alglib.xparams _params)
+    {
+        state = new minbcstate();
+        minbc.minbccreatef(n, x, diffstep, state.innerobj, _params);
+    }
+            
+    public static void minbccreatef(double[] x, double diffstep, out minbcstate state)
+    {
+        int n;
+    
+        state = new minbcstate();
+        n = ap.len(x);
+        minbc.minbccreatef(n, x, diffstep, state.innerobj, null);
+    
+        return;
+    }
+            
+    public static void minbccreatef(double[] x, double diffstep, out minbcstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minbcstate();
+        n = ap.len(x);
+        minbc.minbccreatef(n, x, diffstep, state.innerobj, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function sets boundary constraints for BC optimizer.
+
+    Boundary constraints are inactive by default (after initial creation).
+    They are preserved after algorithm restart with MinBCRestartFrom().
+
+    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 minbcsetbc(minbcstate state, double[] bndl, double[] bndu)
+    {
+    
+        minbc.minbcsetbc(state.innerobj, bndl, bndu, null);
+    }
+    
+    public static void minbcsetbc(minbcstate state, double[] bndl, double[] bndu, alglib.xparams _params)
+    {
+    
+        minbc.minbcsetbc(state.innerobj, bndl, bndu, _params);
+    }
+    
+    /*************************************************************************
+    This function sets stopping conditions for the optimizer.
+
+    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 MinBCSetScale()
+        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 - step vector, dx=X(k+1)-X(k)
+                    * s - scaling coefficients set by MinBCSetScale()
+        MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                    iterations is unlimited.
+
+    Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead
+    to automatic stopping criterion selection.
+
+    NOTE: when SetCond() called with non-zero MaxIts, BC solver may perform
+          slightly more than MaxIts iterations. I.e., MaxIts  sets  non-strict
+          limit on iterations count.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbcsetcond(minbcstate state, double epsg, double epsf, double epsx, int maxits)
+    {
+    
+        minbc.minbcsetcond(state.innerobj, epsg, epsf, epsx, maxits, null);
+    }
+    
+    public static void minbcsetcond(minbcstate state, double epsg, double epsf, double epsx, int maxits, alglib.xparams _params)
+    {
+    
+        minbc.minbcsetcond(state.innerobj, epsg, epsf, epsx, maxits, _params);
+    }
+    
+    /*************************************************************************
+    This function sets scaling coefficients for BC 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  BC  too)  scaling is NOT a form of
+    preconditioning. It just  affects  stopping  conditions.  You  should  set
+    preconditioner  by  separate  call  to  one  of  the  MinBCSetPrec...()
+    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 minbcsetscale(minbcstate state, double[] s)
+    {
+    
+        minbc.minbcsetscale(state.innerobj, s, null);
+    }
+    
+    public static void minbcsetscale(minbcstate state, double[] s, alglib.xparams _params)
+    {
+    
+        minbc.minbcsetscale(state.innerobj, s, _params);
+    }
+    
+    /*************************************************************************
+    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 minbcsetprecdefault(minbcstate state)
+    {
+    
+        minbc.minbcsetprecdefault(state.innerobj, null);
+    }
+    
+    public static void minbcsetprecdefault(minbcstate state, alglib.xparams _params)
+    {
+    
+        minbc.minbcsetprecdefault(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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 minbcsetprecdiag(minbcstate state, double[] d)
+    {
+    
+        minbc.minbcsetprecdiag(state.innerobj, d, null);
+    }
+    
+    public static void minbcsetprecdiag(minbcstate state, double[] d, alglib.xparams _params)
+    {
+    
+        minbc.minbcsetprecdiag(state.innerobj, d, _params);
+    }
+    
+    /*************************************************************************
+    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  MinBCSetScale()
+    call  (before  or after MinBCSetPrecScale() 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 minbcsetprecscale(minbcstate state)
+    {
+    
+        minbc.minbcsetprecscale(state.innerobj, null);
+    }
+    
+    public static void minbcsetprecscale(minbcstate state, alglib.xparams _params)
+    {
+    
+        minbc.minbcsetprecscale(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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 MinBCOptimize().
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbcsetxrep(minbcstate state, bool needxrep)
+    {
+    
+        minbc.minbcsetxrep(state.innerobj, needxrep, null);
+    }
+    
+    public static void minbcsetxrep(minbcstate state, bool needxrep, alglib.xparams _params)
+    {
+    
+        minbc.minbcsetxrep(state.innerobj, needxrep, _params);
+    }
+    
+    /*************************************************************************
+    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  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 minbcsetstpmax(minbcstate state, double stpmax)
+    {
+    
+        minbc.minbcsetstpmax(state.innerobj, stpmax, null);
+    }
+    
+    public static void minbcsetstpmax(minbcstate state, double stpmax, alglib.xparams _params)
+    {
+    
+        minbc.minbcsetstpmax(state.innerobj, stpmax, _params);
+    }
+    
+    /*************************************************************************
+    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 minbciteration(minbcstate state)
+    {
+    
+        return minbc.minbciteration(state.innerobj, null);
+    }
+    
+    public static bool minbciteration(minbcstate state, alglib.xparams _params)
+    {
+    
+        return minbc.minbciteration(state.innerobj, _params);
+    }
+    /*************************************************************************
+    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  MinBCCreate() for analytical gradient or  MinBCCreateF()
+       for numerical differentiation) you should choose appropriate variant of
+       MinBCOptimize() - one  which  accepts  function  AND gradient or one
+       which accepts function ONLY.
+
+       Be careful to choose variant of MinBCOptimize() which corresponds to
+       your optimization scheme! Table below lists different  combinations  of
+       callback (function/gradient) passed to MinBCOptimize()  and specific
+       function used to create optimizer.
+
+
+                         |         USER PASSED TO MinBCOptimize()
+       CREATED WITH      |  function only   |  function and gradient
+       ------------------------------------------------------------
+       MinBCCreateF()    |     works               FAILS
+       MinBCCreate()     |     FAILS               works
+
+       Here "FAIL" denotes inappropriate combinations  of  optimizer  creation
+       function  and  MinBCOptimize()  version.   Attemps   to   use   such
+       combination (for  example,  to  create optimizer with MinBCCreateF()
+       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 minbcoptimize(minbcstate state, ndimensional_func func, ndimensional_rep rep, object obj)
+    {
+        minbcoptimize(state, func, rep, obj, null);
+    }
+    
+    public static void minbcoptimize(minbcstate state, ndimensional_func func, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'minbcoptimize()' (func is null)");
+        while( alglib.minbciteration(state, _params) )
+        {
+            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 'minbcoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    public static void minbcoptimize(minbcstate state, ndimensional_grad grad, ndimensional_rep rep, object obj)
+    {
+        minbcoptimize(state, grad, rep, obj, null);
+    }
+    
+    public static void minbcoptimize(minbcstate state, ndimensional_grad grad, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'minbcoptimize()' (grad is null)");
+        while( alglib.minbciteration(state, _params) )
+        {
+            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 'minbcoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    
+    /*************************************************************************
+    This  function  activates/deactivates verification  of  the  user-supplied
+    analytic gradient.
+
+    Upon  activation  of  this  option  OptGuard  integrity  checker  performs
+    numerical differentiation of your target function  at  the  initial  point
+    (note: future versions may also perform check  at  the  final  point)  and
+    compares numerical gradient with analytic one provided by you.
+
+    If difference is too large, an error flag is set and optimization  session
+    continues. After optimization session is over, you can retrieve the report
+    which  stores  both  gradients  and  specific  components  highlighted  as
+    suspicious by the OptGuard.
+
+    The primary OptGuard report can be retrieved with minbcoptguardresults().
+
+    IMPORTANT: gradient check is a high-overhead option which  will  cost  you
+               about 3*N additional function evaluations. In many cases it may
+               cost as much as the rest of the optimization session.
+
+               YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO
+               CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY.
+
+    NOTE: unlike previous incarnation of the gradient checking code,  OptGuard
+          does NOT interrupt optimization even if it discovers bad gradient.
+
+    INPUT PARAMETERS:
+        State       -   structure used to store algorithm state
+        TestStep    -   verification step used for numerical differentiation:
+                        * TestStep=0 turns verification off
+                        * TestStep>0 activates verification
+                        You should carefully choose TestStep. Value  which  is
+                        too large (so large that  function  behavior  is  non-
+                        cubic at this scale) will lead  to  false  alarms. Too
+                        short step will result in rounding  errors  dominating
+                        numerical derivative.
+
+                        You may use different step for different parameters by
+                        means of setting scale with minbcsetscale().
+
+    === EXPLANATION ==========================================================
+
+    In order to verify gradient algorithm performs following steps:
+      * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i],
+        where X[i] is i-th component of the initial point and S[i] is a  scale
+        of i-th parameter
+      * F(X) is evaluated at these trial points
+      * we perform one more evaluation in the middle point of the interval
+      * we  build  cubic  model using function values and derivatives at trial
+        points and we compare its prediction with actual value in  the  middle
+        point
+
+      -- ALGLIB --
+         Copyright 15.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbcoptguardgradient(minbcstate state, double teststep)
+    {
+    
+        minbc.minbcoptguardgradient(state.innerobj, teststep, null);
+    }
+    
+    public static void minbcoptguardgradient(minbcstate state, double teststep, alglib.xparams _params)
+    {
+    
+        minbc.minbcoptguardgradient(state.innerobj, teststep, _params);
+    }
+    
+    /*************************************************************************
+    This  function  activates/deactivates nonsmoothness monitoring  option  of
+    the  OptGuard  integrity  checker. Smoothness  monitor  silently  observes
+    solution process and tries to detect ill-posed problems, i.e. ones with:
+    a) discontinuous target function (non-C0)
+    b) nonsmooth     target function (non-C1)
+
+    Smoothness monitoring does NOT interrupt optimization  even if it suspects
+    that your problem is nonsmooth. It just sets corresponding  flags  in  the
+    OptGuard report which can be retrieved after optimization is over.
+
+    Smoothness monitoring is a moderate overhead option which often adds  less
+    than 1% to the optimizer running time. Thus, you can use it even for large
+    scale problems.
+
+    NOTE: OptGuard does  NOT  guarantee  that  it  will  always  detect  C0/C1
+          continuity violations.
+
+          First, minor errors are hard to  catch - say, a 0.0001 difference in
+          the model values at two sides of the gap may be due to discontinuity
+          of the model - or simply because the model has changed.
+
+          Second, C1-violations  are  especially  difficult  to  detect  in  a
+          noninvasive way. The optimizer usually  performs  very  short  steps
+          near the nonsmoothness, and differentiation  usually   introduces  a
+          lot of numerical noise.  It  is  hard  to  tell  whether  some  tiny
+          discontinuity in the slope is due to real nonsmoothness or just  due
+          to numerical noise alone.
+
+          Our top priority was to avoid false positives, so in some rare cases
+          minor errors may went unnoticed (however, in most cases they can  be
+          spotted with restart from different initial point).
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+        level   -   monitoring level:
+                    * 0 - monitoring is disabled
+                    * 1 - noninvasive low-overhead monitoring; function values
+                          and/or gradients are recorded, but OptGuard does not
+                          try to perform additional evaluations  in  order  to
+                          get more information about suspicious locations.
+
+    === EXPLANATION ==========================================================
+
+    One major source of headache during optimization  is  the  possibility  of
+    the coding errors in the target function/constraints (or their gradients).
+    Such  errors   most   often   manifest   themselves  as  discontinuity  or
+    nonsmoothness of the target/constraints.
+
+    Another frequent situation is when you try to optimize something involving
+    lots of min() and max() operations, i.e. nonsmooth target. Although not  a
+    coding error, it is nonsmoothness anyway - and smooth  optimizers  usually
+    stop right after encountering nonsmoothness, well before reaching solution.
+
+    OptGuard integrity checker helps you to catch such situations: it monitors
+    function values/gradients being passed  to  the  optimizer  and  tries  to
+    errors. Upon discovering suspicious pair of points it  raises  appropriate
+    flag (and allows you to continue optimization). When optimization is done,
+    you can study OptGuard result.
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbcoptguardsmoothness(minbcstate state, int level)
+    {
+    
+        minbc.minbcoptguardsmoothness(state.innerobj, level, null);
+    }
+    
+    public static void minbcoptguardsmoothness(minbcstate state, int level, alglib.xparams _params)
+    {
+    
+        minbc.minbcoptguardsmoothness(state.innerobj, level, _params);
+    }
+            
+    public static void minbcoptguardsmoothness(minbcstate state)
+    {
+        int level;
+    
+    
+        level = 1;
+        minbc.minbcoptguardsmoothness(state.innerobj, level, null);
+    
+        return;
+    }
+            
+    public static void minbcoptguardsmoothness(minbcstate state, alglib.xparams _params)
+    {
+        int level;
+    
+    
+        level = 1;
+        minbc.minbcoptguardsmoothness(state.innerobj, level, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Results of OptGuard integrity check, should be called  after  optimization
+    session is over.
+
+    === PRIMARY REPORT =======================================================
+
+    OptGuard performs several checks which are intended to catch common errors
+    in the implementation of nonlinear function/gradient:
+    * incorrect analytic gradient
+    * discontinuous (non-C0) target functions (constraints)
+    * nonsmooth     (non-C1) target functions (constraints)
+
+    Each of these checks is activated with appropriate function:
+    * minbcoptguardgradient() for gradient verification
+    * minbcoptguardsmoothness() for C0/C1 checks
+
+    Following flags are set when these errors are suspected:
+    * rep.badgradsuspected, and additionally:
+      * rep.badgradvidx for specific variable (gradient element) suspected
+      * rep.badgradxbase, a point where gradient is tested
+      * rep.badgraduser, user-provided gradient  (stored  as  2D  matrix  with
+        single row in order to make  report  structure  compatible  with  more
+        complex optimizers like MinNLC or MinLM)
+      * rep.badgradnum,   reference    gradient    obtained    via   numerical
+        differentiation (stored as  2D matrix with single row in order to make
+        report structure compatible with more complex optimizers  like  MinNLC
+        or MinLM)
+    * rep.nonc0suspected
+    * rep.nonc1suspected
+
+    === ADDITIONAL REPORTS/LOGS ==============================================
+
+    Several different tests are performed to catch C0/C1 errors, you can  find
+    out specific test signaled error by looking to:
+    * rep.nonc0test0positive, for non-C0 test #0
+    * rep.nonc1test0positive, for non-C1 test #0
+    * rep.nonc1test1positive, for non-C1 test #1
+
+    Additional information (including line search logs)  can  be  obtained  by
+    means of:
+    * minbcoptguardnonc1test0results()
+    * minbcoptguardnonc1test1results()
+    which return detailed error reports, specific points where discontinuities
+    were found, and so on.
+
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        rep     -   generic OptGuard report;  more  detailed  reports  can  be
+                    retrieved with other functions.
+
+    NOTE: false negatives (nonsmooth problems are not identified as  nonsmooth
+          ones) are possible although unlikely.
+
+          The reason  is  that  you  need  to  make several evaluations around
+          nonsmoothness  in  order  to  accumulate  enough  information  about
+          function curvature. Say, if you start right from the nonsmooth point,
+          optimizer simply won't get enough data to understand what  is  going
+          wrong before it terminates due to abrupt changes in the  derivative.
+          It is also  possible  that  "unlucky"  step  will  move  us  to  the
+          termination too quickly.
+
+          Our current approach is to have less than 0.1%  false  negatives  in
+          our test examples  (measured  with  multiple  restarts  from  random
+          points), and to have exactly 0% false positives.
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbcoptguardresults(minbcstate state, out optguardreport rep)
+    {
+        rep = new optguardreport();
+        minbc.minbcoptguardresults(state.innerobj, rep.innerobj, null);
+    }
+    
+    public static void minbcoptguardresults(minbcstate state, out optguardreport rep, alglib.xparams _params)
+    {
+        rep = new optguardreport();
+        minbc.minbcoptguardresults(state.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Detailed results of the OptGuard integrity check for nonsmoothness test #0
+
+    Nonsmoothness (non-C1) test #0 studies  function  values  (not  gradient!)
+    obtained during line searches and monitors  behavior  of  the  directional
+    derivative estimate.
+
+    This test is less powerful than test #1, but it does  not  depend  on  the
+    gradient values and thus it is more robust against artifacts introduced by
+    numerical differentiation.
+
+    Two reports are returned:
+    * a "strongest" one, corresponding  to  line   search  which  had  highest
+      value of the nonsmoothness indicator
+    * a "longest" one, corresponding to line search which  had  more  function
+      evaluations, and thus is more detailed
+
+    In both cases following fields are returned:
+
+    * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+      did not notice anything (in the latter cases fields below are empty).
+    * x0[], d[] - arrays of length N which store initial point  and  direction
+      for line search (d[] can be normalized, but does not have to)
+    * stp[], f[] - arrays of length CNT which store step lengths and  function
+      values at these points; f[i] is evaluated in x0+stp[i]*d.
+    * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+      between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+      with  most  likely  position  of  the  violation  between  stpidxa+1 and
+      stpidxa+2.
+
+    ==========================================================================
+    = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+    =                   see where C1 continuity is violated.
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        strrep  -   C1 test #0 "strong" report
+        lngrep  -   C1 test #0 "long" report
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbcoptguardnonc1test0results(minbcstate state, out optguardnonc1test0report strrep, out optguardnonc1test0report lngrep)
+    {
+        strrep = new optguardnonc1test0report();
+        lngrep = new optguardnonc1test0report();
+        minbc.minbcoptguardnonc1test0results(state.innerobj, strrep.innerobj, lngrep.innerobj, null);
+    }
+    
+    public static void minbcoptguardnonc1test0results(minbcstate state, out optguardnonc1test0report strrep, out optguardnonc1test0report lngrep, alglib.xparams _params)
+    {
+        strrep = new optguardnonc1test0report();
+        lngrep = new optguardnonc1test0report();
+        minbc.minbcoptguardnonc1test0results(state.innerobj, strrep.innerobj, lngrep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Detailed results of the OptGuard integrity check for nonsmoothness test #1
+
+    Nonsmoothness (non-C1)  test  #1  studies  individual  components  of  the
+    gradient computed during line search.
+
+    When precise analytic gradient is provided this test is more powerful than
+    test #0  which  works  with  function  values  and  ignores  user-provided
+    gradient.  However,  test  #0  becomes  more   powerful   when   numerical
+    differentiation is employed (in such cases test #1 detects  higher  levels
+    of numerical noise and becomes too conservative).
+
+    This test also tells specific components of the gradient which violate  C1
+    continuity, which makes it more informative than #0, which just tells that
+    continuity is violated.
+
+    Two reports are returned:
+    * a "strongest" one, corresponding  to  line   search  which  had  highest
+      value of the nonsmoothness indicator
+    * a "longest" one, corresponding to line search which  had  more  function
+      evaluations, and thus is more detailed
+
+    In both cases following fields are returned:
+
+    * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+      did not notice anything (in the latter cases fields below are empty).
+    * vidx - is an index of the variable in [0,N) with nonsmooth derivative
+    * x0[], d[] - arrays of length N which store initial point  and  direction
+      for line search (d[] can be normalized, but does not have to)
+    * stp[], g[] - arrays of length CNT which store step lengths and  gradient
+      values at these points; g[i] is evaluated in  x0+stp[i]*d  and  contains
+      vidx-th component of the gradient.
+    * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+      between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+      with  most  likely  position  of  the  violation  between  stpidxa+1 and
+      stpidxa+2.
+
+    ==========================================================================
+    = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+    =                   see where C1 continuity is violated.
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        strrep  -   C1 test #1 "strong" report
+        lngrep  -   C1 test #1 "long" report
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbcoptguardnonc1test1results(minbcstate state, out optguardnonc1test1report strrep, out optguardnonc1test1report lngrep)
+    {
+        strrep = new optguardnonc1test1report();
+        lngrep = new optguardnonc1test1report();
+        minbc.minbcoptguardnonc1test1results(state.innerobj, strrep.innerobj, lngrep.innerobj, null);
+    }
+    
+    public static void minbcoptguardnonc1test1results(minbcstate state, out optguardnonc1test1report strrep, out optguardnonc1test1report lngrep, alglib.xparams _params)
+    {
+        strrep = new optguardnonc1test1report();
+        lngrep = new optguardnonc1test1report();
+        minbc.minbcoptguardnonc1test1results(state.innerobj, strrep.innerobj, lngrep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    BC 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:
+                    * -8    internal integrity control  detected  infinite or
+                            NAN   values   in   function/gradient.   Abnormal
+                            termination signalled.
+                    * -3   inconsistent constraints.
+                    *  1   relative function improvement is no more than EpsF.
+                    *  2   scaled step is no more than EpsX.
+                    *  4   scaled gradient norm is no more than EpsG.
+                    *  5   MaxIts steps was taken
+                    *  8   terminated by user who called minbcrequesttermination().
+                           X contains point which was "current accepted"  when
+                           termination request was submitted.
+                    More information about fields of this  structure  can  be
+                    found in the comments on MinBCReport datatype.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbcresults(minbcstate state, out double[] x, out minbcreport rep)
+    {
+        x = new double[0];
+        rep = new minbcreport();
+        minbc.minbcresults(state.innerobj, ref x, rep.innerobj, null);
+    }
+    
+    public static void minbcresults(minbcstate state, out double[] x, out minbcreport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new minbcreport();
+        minbc.minbcresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    BC results
+
+    Buffered implementation of MinBCResults() 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 minbcresultsbuf(minbcstate state, ref double[] x, minbcreport rep)
+    {
+    
+        minbc.minbcresultsbuf(state.innerobj, ref x, rep.innerobj, null);
+    }
+    
+    public static void minbcresultsbuf(minbcstate state, ref double[] x, minbcreport rep, alglib.xparams _params)
+    {
+    
+        minbc.minbcresultsbuf(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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 MinBCCreate call.
+        X       -   new starting point.
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbcrestartfrom(minbcstate state, double[] x)
+    {
+    
+        minbc.minbcrestartfrom(state.innerobj, x, null);
+    }
+    
+    public static void minbcrestartfrom(minbcstate state, double[] x, alglib.xparams _params)
+    {
+    
+        minbc.minbcrestartfrom(state.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine submits request for termination of running  optimizer.  It
+    should be called from user-supplied callback when user decides that it  is
+    time to "smoothly" terminate optimization process.  As  result,  optimizer
+    stops at point which was "current accepted" when termination  request  was
+    submitted and returns error code 8 (successful termination).
+
+    INPUT PARAMETERS:
+        State   -   optimizer structure
+
+    NOTE: after  request  for  termination  optimizer  may   perform   several
+          additional calls to user-supplied callbacks. It does  NOT  guarantee
+          to stop immediately - it just guarantees that these additional calls
+          will be discarded later.
+
+    NOTE: calling this function on optimizer which is NOT running will have no
+          effect.
+
+    NOTE: multiple calls to this function are possible. First call is counted,
+          subsequent calls are silently ignored.
+
+      -- ALGLIB --
+         Copyright 08.10.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minbcrequesttermination(minbcstate state)
+    {
+    
+        minbc.minbcrequesttermination(state.innerobj, null);
+    }
+    
+    public static void minbcrequesttermination(minbcstate state, alglib.xparams _params)
+    {
+    
+        minbc.minbcrequesttermination(state.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This object stores nonlinear optimizer state.
+    You should use functions provided by MinNS subpackage to work  with  this
+    object
+    *************************************************************************/
+    public class minnsstate : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        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[,] j { get { return _innerobj.j; } }
+        public double[] x { get { return _innerobj.x; } }
+    
+        public minnsstate()
+        {
+            _innerobj = new minns.minnsstate();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minnsstate((minns.minnsstate)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minns.minnsstate _innerobj;
+        public minns.minnsstate innerobj { get { return _innerobj; } }
+        public minnsstate(minns.minnsstate obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+    This structure stores optimization report:
+    * IterationsCount           total number of inner iterations
+    * NFEV                      number of gradient evaluations
+    * TerminationType           termination type (see below)
+    * CErr                      maximum violation of all types of constraints
+    * LCErr                     maximum violation of linear constraints
+    * NLCErr                    maximum violation of nonlinear constraints
+
+    TERMINATION CODES
+
+    TerminationType field contains completion code, which can be:
+      -8    internal integrity control detected  infinite  or  NAN  values  in
+            function/gradient. Abnormal termination signalled.
+      -3    box constraints are inconsistent
+      -1    inconsistent parameters were passed:
+            * penalty parameter for minnssetalgoags() is zero,
+              but we have nonlinear constraints set by minnssetnlc()
+       2    sampling radius decreased below epsx
+       5    MaxIts steps was taken
+       7    stopping conditions are too stringent,
+            further improvement is impossible,
+            X contains best point found so far.
+       8    User requested termination via MinNSRequestTermination()
+
+    Other fields of this structure are not documented and should not be used!
+    *************************************************************************/
+    public class minnsreport : alglibobject
+    {
+        //
+        // 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 double cerr { get { return _innerobj.cerr; } set { _innerobj.cerr = value; } }
+        public double lcerr { get { return _innerobj.lcerr; } set { _innerobj.lcerr = value; } }
+        public double nlcerr { get { return _innerobj.nlcerr; } set { _innerobj.nlcerr = value; } }
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+        public int varidx { get { return _innerobj.varidx; } set { _innerobj.varidx = value; } }
+        public int funcidx { get { return _innerobj.funcidx; } set { _innerobj.funcidx = value; } }
+    
+        public minnsreport()
+        {
+            _innerobj = new minns.minnsreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minnsreport((minns.minnsreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private minns.minnsreport _innerobj;
+        public minns.minnsreport innerobj { get { return _innerobj; } }
+        public minnsreport(minns.minnsreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+    
+    /*************************************************************************
+                      NONSMOOTH NONCONVEX OPTIMIZATION
+                SUBJECT TO BOX/LINEAR/NONLINEAR-NONSMOOTH CONSTRAINTS
+
+    DESCRIPTION:
+
+    The  subroutine  minimizes  function   F(x)  of N arguments subject to any
+    combination of:
+    * bound constraints
+    * linear inequality constraints
+    * linear equality constraints
+    * nonlinear equality constraints Gi(x)=0
+    * nonlinear inequality constraints Hi(x)<=0
+
+    IMPORTANT: see MinNSSetAlgoAGS for important  information  on  performance
+               restrictions of AGS solver.
+
+    REQUIREMENTS:
+    * starting point X0 must be feasible or not too far away from the feasible
+      set
+    * F(), G(), H() are continuous, locally Lipschitz  and  continuously  (but
+      not necessarily twice) differentiable in an open dense  subset  of  R^N.
+      Functions F(), G() and H() may be nonsmooth and non-convex.
+      Informally speaking, it means  that  functions  are  composed  of  large
+      differentiable "patches" with nonsmoothness having  place  only  at  the
+      boundaries between these "patches".
+      Most real-life nonsmooth  functions  satisfy  these  requirements.  Say,
+      anything which involves finite number of abs(), min() and max() is  very
+      likely to pass the test.
+      Say, it is possible to optimize anything of the following:
+      * f=abs(x0)+2*abs(x1)
+      * f=max(x0,x1)
+      * f=sin(max(x0,x1)+abs(x2))
+    * for nonlinearly constrained problems: F()  must  be  bounded from  below
+      without nonlinear constraints (this requirement is due to the fact that,
+      contrary to box and linear constraints, nonlinear ones  require  special
+      handling).
+    * user must provide function value and gradient for F(), H(), G()  at  all
+      points where function/gradient can be calculated. If optimizer  requires
+      value exactly at the boundary between "patches" (say, at x=0 for f=abs(x)),
+      where gradient is not defined, user may resolve tie arbitrarily (in  our
+      case - return +1 or -1 at its discretion).
+    * NS solver supports numerical differentiation, i.e. it may  differentiate
+      your function for you,  but  it  results  in  2N  increase  of  function
+      evaluations. Not recommended unless you solve really small problems. See
+      minnscreatef() for more information on this functionality.
+
+    USAGE:
+
+    1. User initializes algorithm state with MinNSCreate() call  and   chooses
+       what NLC solver to use. There is some solver which is used by  default,
+       with default settings, but you should NOT rely on  default  choice.  It
+       may change in future releases of ALGLIB without notice, and no one  can
+       guarantee that new solver will be  able  to  solve  your  problem  with
+       default settings.
+
+       From the other side, if you choose solver explicitly, you can be pretty
+       sure that it will work with new ALGLIB releases.
+
+       In the current release following solvers can be used:
+       * AGS solver (activated with MinNSSetAlgoAGS() function)
+
+    2. User adds boundary and/or linear and/or nonlinear constraints by  means
+       of calling one of the following functions:
+       a) MinNSSetBC() for boundary constraints
+       b) MinNSSetLC() for linear constraints
+       c) MinNSSetNLC() for nonlinear constraints
+       You may combine (a), (b) and (c) in one optimization problem.
+
+    3. User sets scale of the variables with MinNSSetScale() function. It   is
+       VERY important to set  scale  of  the  variables,  because  nonlinearly
+       constrained problems are hard to solve when variables are badly scaled.
+
+    4. User sets stopping conditions with MinNSSetCond().
+
+    5. Finally, user calls MinNSOptimize()  function  which  takes   algorithm
+       state and pointer (delegate, etc) to callback function which calculates
+       F/G/H.
+
+    7. User calls MinNSResults() to get solution
+
+    8. Optionally user may call MinNSRestartFrom() to solve   another  problem
+       with same N but another starting point. MinNSRestartFrom()  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[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
+
+    NOTE: minnscreatef() function may be used if  you  do  not  have  analytic
+          gradient.   This   function  creates  solver  which  uses  numerical
+          differentiation with user-specified step.
+
+      -- ALGLIB --
+         Copyright 18.05.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnscreate(int n, double[] x, out minnsstate state)
+    {
+        state = new minnsstate();
+        minns.minnscreate(n, x, state.innerobj, null);
+    }
+    
+    public static void minnscreate(int n, double[] x, out minnsstate state, alglib.xparams _params)
+    {
+        state = new minnsstate();
+        minns.minnscreate(n, x, state.innerobj, _params);
+    }
+            
+    public static void minnscreate(double[] x, out minnsstate state)
+    {
+        int n;
+    
+        state = new minnsstate();
+        n = ap.len(x);
+        minns.minnscreate(n, x, state.innerobj, null);
+    
+        return;
+    }
+            
+    public static void minnscreate(double[] x, out minnsstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minnsstate();
+        n = ap.len(x);
+        minns.minnscreate(n, x, state.innerobj, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Version of minnscreatef() which uses numerical differentiation. I.e.,  you
+    do not have to calculate derivatives yourself. However, this version needs
+    2N times more function evaluations.
+
+    2-point differentiation formula is  used,  because  more  precise  4-point
+    formula is unstable when used on non-smooth functions.
+
+    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[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.
+        DiffStep-   differentiation  step,  DiffStep>0.   Algorithm   performs
+                    numerical differentiation  with  step  for  I-th  variable
+                    being equal to DiffStep*S[I] (here S[] is a  scale vector,
+                    set by minnssetscale() function).
+                    Do not use  too  small  steps,  because  it  may  lead  to
+                    catastrophic cancellation during intermediate calculations.
+
+    OUTPUT PARAMETERS:
+        State   -   structure stores algorithm state
+
+      -- ALGLIB --
+         Copyright 18.05.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnscreatef(int n, double[] x, double diffstep, out minnsstate state)
+    {
+        state = new minnsstate();
+        minns.minnscreatef(n, x, diffstep, state.innerobj, null);
+    }
+    
+    public static void minnscreatef(int n, double[] x, double diffstep, out minnsstate state, alglib.xparams _params)
+    {
+        state = new minnsstate();
+        minns.minnscreatef(n, x, diffstep, state.innerobj, _params);
+    }
+            
+    public static void minnscreatef(double[] x, double diffstep, out minnsstate state)
+    {
+        int n;
+    
+        state = new minnsstate();
+        n = ap.len(x);
+        minns.minnscreatef(n, x, diffstep, state.innerobj, null);
+    
+        return;
+    }
+            
+    public static void minnscreatef(double[] x, double diffstep, out minnsstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minnsstate();
+        n = ap.len(x);
+        minns.minnscreatef(n, x, diffstep, state.innerobj, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function sets boundary constraints.
+
+    Boundary constraints are inactive by default (after initial creation).
+    They are preserved after algorithm restart with minnsrestartfrom().
+
+    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: AGS 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 18.05.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnssetbc(minnsstate state, double[] bndl, double[] bndu)
+    {
+    
+        minns.minnssetbc(state.innerobj, bndl, bndu, null);
+    }
+    
+    public static void minnssetbc(minnsstate state, double[] bndl, double[] bndu, alglib.xparams _params)
+    {
+    
+        minns.minnssetbc(state.innerobj, bndl, bndu, _params);
+    }
+    
+    /*************************************************************************
+    This function sets linear constraints.
+
+    Linear constraints are inactive by default (after initial creation).
+    They are preserved after algorithm restart with minnsrestartfrom().
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with minnscreate() 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: linear (non-bound) constraints are satisfied only approximately:
+
+    * there always exists some minor violation (about current sampling  radius
+      in magnitude during optimization, about EpsX in the solution) due to use
+      of penalty method to handle constraints.
+    * 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 18.05.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnssetlc(minnsstate state, double[,] c, int[] ct, int k)
+    {
+    
+        minns.minnssetlc(state.innerobj, c, ct, k, null);
+    }
+    
+    public static void minnssetlc(minnsstate state, double[,] c, int[] ct, int k, alglib.xparams _params)
+    {
+    
+        minns.minnssetlc(state.innerobj, c, ct, k, _params);
+    }
+            
+    public static void minnssetlc(minnsstate state, double[,] c, int[] ct)
+    {
+        int k;
+        if( (ap.rows(c)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'minnssetlc': looks like one of arguments has wrong size");
+    
+        k = ap.rows(c);
+        minns.minnssetlc(state.innerobj, c, ct, k, null);
+    
+        return;
+    }
+            
+    public static void minnssetlc(minnsstate state, double[,] c, int[] ct, alglib.xparams _params)
+    {
+        int k;
+        if( (ap.rows(c)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'minnssetlc': looks like one of arguments has wrong size");
+    
+        k = ap.rows(c);
+        minns.minnssetlc(state.innerobj, c, ct, k, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function sets nonlinear constraints.
+
+    In fact, this function sets NUMBER of nonlinear  constraints.  Constraints
+    itself (constraint functions) are passed to minnsoptimize() method.   This
+    method requires user-defined vector function F[]  and  its  Jacobian  J[],
+    where:
+    * first component of F[] and first row  of  Jacobian  J[]  correspond   to
+      function being minimized
+    * next NLEC components of F[] (and rows  of  J)  correspond  to  nonlinear
+      equality constraints G_i(x)=0
+    * next NLIC components of F[] (and rows  of  J)  correspond  to  nonlinear
+      inequality constraints H_i(x)<=0
+
+    NOTE: you may combine nonlinear constraints with linear/boundary ones.  If
+          your problem has mixed constraints, you  may explicitly specify some
+          of them as linear ones. It may help optimizer to  handle  them  more
+          efficiently.
+
+    INPUT PARAMETERS:
+        State   -   structure previously allocated with minnscreate() call.
+        NLEC    -   number of Non-Linear Equality Constraints (NLEC), >=0
+        NLIC    -   number of Non-Linear Inquality Constraints (NLIC), >=0
+
+    NOTE 1: nonlinear constraints are satisfied only  approximately!   It   is
+            possible   that  algorithm  will  evaluate  function  outside   of
+            the feasible area!
+
+    NOTE 2: algorithm scales variables  according  to   scale   specified   by
+            minnssetscale()  function,  so  it can handle problems with  badly
+            scaled variables (as long as we KNOW their scales).
+
+            However,  there  is  no  way  to  automatically  scale   nonlinear
+            constraints Gi(x) and Hi(x). Inappropriate scaling  of  Gi/Hi  may
+            ruin convergence. Solving problem with  constraint  "1000*G0(x)=0"
+            is NOT same as solving it with constraint "0.001*G0(x)=0".
+
+            It  means  that  YOU  are  the  one who is responsible for correct
+            scaling of nonlinear constraints Gi(x) and Hi(x). We recommend you
+            to scale nonlinear constraints in such way that I-th component  of
+            dG/dX (or dH/dx) has approximately unit  magnitude  (for  problems
+            with unit scale)  or  has  magnitude approximately equal to 1/S[i]
+            (where S is a scale set by minnssetscale() function).
+
+    NOTE 3: nonlinear constraints are always hard to handle,  no  matter  what
+            algorithm you try to use. Even basic box/linear constraints modify
+            function  curvature   by  adding   valleys  and  ridges.  However,
+            nonlinear constraints add valleys which are very  hard  to  follow
+            due to their "curved" nature.
+
+            It means that optimization with single nonlinear constraint may be
+            significantly slower than optimization with multiple linear  ones.
+            It is normal situation, and we recommend you to  carefully  choose
+            Rho parameter of minnssetalgoags(), because too  large  value  may
+            slow down convergence.
+
+
+      -- ALGLIB --
+         Copyright 18.05.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnssetnlc(minnsstate state, int nlec, int nlic)
+    {
+    
+        minns.minnssetnlc(state.innerobj, nlec, nlic, null);
+    }
+    
+    public static void minnssetnlc(minnsstate state, int nlec, int nlic, alglib.xparams _params)
+    {
+    
+        minns.minnssetnlc(state.innerobj, nlec, nlic, _params);
+    }
+    
+    /*************************************************************************
+    This function sets stopping conditions for iterations of optimizer.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        EpsX    -   >=0
+                    The AGS solver finishes its work if  on  k+1-th  iteration
+                    sampling radius decreases below EpsX.
+        MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                    iterations is unlimited.
+
+    Passing EpsX=0  and  MaxIts=0  (simultaneously)  will  lead  to  automatic
+    stopping criterion selection. We do not recommend you to rely  on  default
+    choice in production code.
+
+      -- ALGLIB --
+         Copyright 18.05.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnssetcond(minnsstate state, double epsx, int maxits)
+    {
+    
+        minns.minnssetcond(state.innerobj, epsx, maxits, null);
+    }
+    
+    public static void minnssetcond(minnsstate state, double epsx, int maxits, alglib.xparams _params)
+    {
+    
+        minns.minnssetcond(state.innerobj, epsx, maxits, _params);
+    }
+    
+    /*************************************************************************
+    This function sets scaling coefficients for NLC 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].
+
+    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 18.05.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnssetscale(minnsstate state, double[] s)
+    {
+    
+        minns.minnssetscale(state.innerobj, s, null);
+    }
+    
+    public static void minnssetscale(minnsstate state, double[] s, alglib.xparams _params)
+    {
+    
+        minns.minnssetscale(state.innerobj, s, _params);
+    }
+    
+    /*************************************************************************
+    This function tells MinNS unit to use  AGS  (adaptive  gradient  sampling)
+    algorithm for nonsmooth constrained  optimization.  This  algorithm  is  a
+    slight modification of one described in  "An  Adaptive  Gradient  Sampling
+    Algorithm for Nonsmooth Optimization" by Frank E. Curtisy and Xiaocun Quez.
+
+    This optimizer has following benefits and drawbacks:
+    + robustness; it can be used with nonsmooth and nonconvex functions.
+    + relatively easy tuning; most of the metaparameters are easy to select.
+    - it has convergence of steepest descent, slower than CG/LBFGS.
+    - each iteration involves evaluation of ~2N gradient values  and  solution
+      of 2Nx2N quadratic programming problem, which  limits  applicability  of
+      algorithm by small-scale problems (up to 50-100).
+
+    IMPORTANT: this  algorithm  has  convergence  guarantees,   i.e.  it  will
+               steadily move towards some stationary point of the function.
+
+               However, "stationary point" does not  always  mean  "solution".
+               Nonsmooth problems often have "flat spots",  i.e.  areas  where
+               function do not change at all. Such "flat spots" are stationary
+               points by definition, and algorithm may be caught here.
+
+               Nonsmooth CONVEX tasks are not prone to  this  problem. Say, if
+               your function has form f()=MAX(f0,f1,...), and f_i are  convex,
+               then f() is convex too and you have guaranteed  convergence  to
+               solution.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        Radius  -   initial sampling radius, >=0.
+
+                    Internally multiplied  by  vector of  per-variable  scales
+                    specified by minnssetscale()).
+
+                    You should select relatively large sampling radius, roughly
+                    proportional to scaled length of the first  steps  of  the
+                    algorithm. Something close to 0.1 in magnitude  should  be
+                    good for most problems.
+
+                    AGS solver can automatically decrease radius, so too large
+                    radius is  not a problem (assuming that you  won't  choose
+                    so large radius that algorithm  will  sample  function  in
+                    too far away points, where gradient value is irrelevant).
+
+                    Too small radius won't cause algorithm to fail, but it may
+                    slow down algorithm (it may  have  to  perform  too  short
+                    steps).
+        Penalty -   penalty coefficient for nonlinear constraints:
+                    * for problem with nonlinear constraints  should  be  some
+                      problem-specific  positive   value,  large  enough  that
+                      penalty term changes shape of the function.
+                      Starting  from  some  problem-specific   value   penalty
+                      coefficient becomes  large  enough  to  exactly  enforce
+                      nonlinear constraints;  larger  values  do  not  improve
+                      precision.
+                      Increasing it too much may slow down convergence, so you
+                      should choose it carefully.
+                    * can be zero for problems WITHOUT  nonlinear  constraints
+                      (i.e. for unconstrained ones or ones with  just  box  or
+                      linear constraints)
+                    * if you specify zero value for problem with at least  one
+                      nonlinear  constraint,  algorithm  will  terminate  with
+                      error code -1.
+
+    ALGORITHM OUTLINE
+
+    The very basic outline of unconstrained AGS algorithm is given below:
+
+    0. If sampling radius is below EpsX  or  we  performed  more  then  MaxIts
+       iterations - STOP.
+    1. sample O(N) gradient values at random locations  around  current point;
+       informally speaking, this sample is an implicit piecewise  linear model
+       of the function, although algorithm formulation does  not  mention that
+       explicitly
+    2. solve quadratic programming problem in order to find descent direction
+    3. if QP solver tells us that we  are  near  solution,  decrease  sampling
+       radius and move to (0)
+    4. perform backtracking line search
+    5. after moving to new point, goto (0)
+
+    As for the constraints:
+    * box constraints are handled exactly  by  modification  of  the  function
+      being minimized
+    * linear/nonlinear constraints are handled by adding L1  penalty.  Because
+      our solver can handle nonsmoothness, we can  use  L1  penalty  function,
+      which is an exact one  (i.e.  exact  solution  is  returned  under  such
+      penalty).
+    * penalty coefficient for  linear  constraints  is  chosen  automatically;
+      however, penalty coefficient for nonlinear constraints must be specified
+      by user.
+
+      -- ALGLIB --
+         Copyright 18.05.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnssetalgoags(minnsstate state, double radius, double penalty)
+    {
+    
+        minns.minnssetalgoags(state.innerobj, radius, penalty, null);
+    }
+    
+    public static void minnssetalgoags(minnsstate state, double radius, double penalty, alglib.xparams _params)
+    {
+    
+        minns.minnssetalgoags(state.innerobj, radius, penalty, _params);
+    }
+    
+    /*************************************************************************
+    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 minnsoptimize().
+
+      -- ALGLIB --
+         Copyright 28.11.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnssetxrep(minnsstate state, bool needxrep)
+    {
+    
+        minns.minnssetxrep(state.innerobj, needxrep, null);
+    }
+    
+    public static void minnssetxrep(minnsstate state, bool needxrep, alglib.xparams _params)
+    {
+    
+        minns.minnssetxrep(state.innerobj, needxrep, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine submits request for termination of running  optimizer.  It
+    should be called from user-supplied callback when user decides that it  is
+    time to "smoothly" terminate optimization process.  As  result,  optimizer
+    stops at point which was "current accepted" when termination  request  was
+    submitted and returns error code 8 (successful termination).
+
+    INPUT PARAMETERS:
+        State   -   optimizer structure
+
+    NOTE: after  request  for  termination  optimizer  may   perform   several
+          additional calls to user-supplied callbacks. It does  NOT  guarantee
+          to stop immediately - it just guarantees that these additional calls
+          will be discarded later.
+
+    NOTE: calling this function on optimizer which is NOT running will have no
+          effect.
+
+    NOTE: multiple calls to this function are possible. First call is counted,
+          subsequent calls are silently ignored.
+
+      -- ALGLIB --
+         Copyright 18.05.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnsrequesttermination(minnsstate state)
+    {
+    
+        minns.minnsrequesttermination(state.innerobj, null);
+    }
+    
+    public static void minnsrequesttermination(minnsstate state, alglib.xparams _params)
+    {
+    
+        minns.minnsrequesttermination(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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 minnsiteration(minnsstate state)
+    {
+    
+        return minns.minnsiteration(state.innerobj, null);
+    }
+    
+    public static bool minnsiteration(minnsstate state, alglib.xparams _params)
+    {
+    
+        return minns.minnsiteration(state.innerobj, _params);
+    }
+    /*************************************************************************
+    This family of functions is used to launcn iterations of nonlinear optimizer
+
+    These functions accept following parameters:
+        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. This function has two different implementations: one which  uses  exact
+       (analytical) user-supplied Jacobian, and one which uses  only  function
+       vector and numerically  differentiates  function  in  order  to  obtain
+       gradient.
+
+       Depending  on  the  specific  function  used to create optimizer object
+       you should choose appropriate variant of  minnsoptimize() -  one  which
+       accepts function AND Jacobian or one which accepts ONLY function.
+
+       Be careful to choose variant of minnsoptimize()  which  corresponds  to
+       your optimization scheme! Table below lists different  combinations  of
+       callback (function/gradient) passed to minnsoptimize()    and  specific
+       function used to create optimizer.
+
+
+                         |         USER PASSED TO minnsoptimize()
+       CREATED WITH      |  function only   |  function and gradient
+       ------------------------------------------------------------
+       minnscreatef()    |     works               FAILS
+       minnscreate()     |     FAILS               works
+
+       Here "FAILS" denotes inappropriate combinations  of  optimizer creation
+       function  and  minnsoptimize()  version.   Attemps   to    use     such
+       combination will lead to exception. Either  you  did  not pass gradient
+       when it WAS needed or you passed gradient when it was NOT needed.
+
+      -- ALGLIB --
+         Copyright 18.05.2015 by Bochkanov Sergey
+
+    *************************************************************************/
+    public static void minnsoptimize(minnsstate state, ndimensional_fvec  fvec, ndimensional_rep rep, object obj)
+    {
+        minnsoptimize(state, fvec, rep, obj, null);
+    }
+    
+    public static void minnsoptimize(minnsstate state, ndimensional_fvec  fvec, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( fvec==null )
+            throw new alglibexception("ALGLIB: error in 'minnsoptimize()' (fvec is null)");
+        while( alglib.minnsiteration(state, _params) )
+        {
+            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 'minnsoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    public static void minnsoptimize(minnsstate state, ndimensional_jac  jac, ndimensional_rep rep, object obj)
+    {
+        minnsoptimize(state, jac, rep, obj, null);
+    }
+    
+    public static void minnsoptimize(minnsstate state, ndimensional_jac  jac, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( jac==null )
+            throw new alglibexception("ALGLIB: error in 'minnsoptimize()' (jac is null)");
+        while( alglib.minnsiteration(state, _params) )
+        {
+            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 'minnsoptimize' (some derivatives were not provided?)");
+        }
+    }
+
+
+    
+    /*************************************************************************
+    MinNS 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:
+                    * -8   internal integrity control  detected  infinite  or
+                           NAN   values   in   function/gradient.    Abnormal
+                           termination signalled.
+                    * -3   box constraints are inconsistent
+                    * -1   inconsistent parameters were passed:
+                           * penalty parameter for minnssetalgoags() is zero,
+                             but we have nonlinear constraints set by minnssetnlc()
+                    *  2   sampling radius decreased below epsx
+                    *  7    stopping conditions are too stringent,
+                            further improvement is impossible,
+                            X contains best point found so far.
+                    *  8    User requested termination via minnsrequesttermination()
+
+      -- ALGLIB --
+         Copyright 18.05.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnsresults(minnsstate state, out double[] x, out minnsreport rep)
+    {
+        x = new double[0];
+        rep = new minnsreport();
+        minns.minnsresults(state.innerobj, ref x, rep.innerobj, null);
+    }
+    
+    public static void minnsresults(minnsstate state, out double[] x, out minnsreport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new minnsreport();
+        minns.minnsresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+
+    Buffered implementation of minnsresults() 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 18.05.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnsresultsbuf(minnsstate state, ref double[] x, minnsreport rep)
+    {
+    
+        minns.minnsresultsbuf(state.innerobj, ref x, rep.innerobj, null);
+    }
+    
+    public static void minnsresultsbuf(minnsstate state, ref double[] x, minnsreport rep, alglib.xparams _params)
+    {
+    
+        minns.minnsresultsbuf(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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 minnscreate() call.
+        X       -   new starting point.
+
+      -- ALGLIB --
+         Copyright 18.05.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minnsrestartfrom(minnsstate state, double[] x)
+    {
+    
+        minns.minnsrestartfrom(state.innerobj, x, null);
+    }
+    
+    public static void minnsrestartfrom(minnsstate state, double[] x, alglib.xparams _params)
+    {
+    
+        minns.minnsrestartfrom(state.innerobj, x, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class minasastate : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minasastate((mincomp.minasastate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minasareport((mincomp.minasareport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void minlbfgssetdefaultpreconditioner(minlbfgsstate state, alglib.xparams _params)
+    {
+    
+        mincomp.minlbfgssetdefaultpreconditioner(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlbfgssetcholeskypreconditioner(minlbfgsstate state, double[,] p, bool isupper, alglib.xparams _params)
+    {
+    
+        mincomp.minlbfgssetcholeskypreconditioner(state.innerobj, p, isupper, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minbleicsetbarrierwidth(minbleicstate state, double mu, alglib.xparams _params)
+    {
+    
+        mincomp.minbleicsetbarrierwidth(state.innerobj, mu, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minbleicsetbarrierdecay(minbleicstate state, double mudecay, alglib.xparams _params)
+    {
+    
+        mincomp.minbleicsetbarrierdecay(state.innerobj, mudecay, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minasacreate(int n, double[] x, double[] bndl, double[] bndu, out minasastate state, alglib.xparams _params)
+    {
+        state = new minasastate();
+        mincomp.minasacreate(n, x, bndl, bndu, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void minasacreate(double[] x, double[] bndl, double[] bndu, out minasastate state, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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, null);
+    }
+    
+    public static void minasasetcond(minasastate state, double epsg, double epsf, double epsx, int maxits, alglib.xparams _params)
+    {
+    
+        mincomp.minasasetcond(state.innerobj, epsg, epsf, epsx, maxits, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minasasetxrep(minasastate state, bool needxrep, alglib.xparams _params)
+    {
+    
+        mincomp.minasasetxrep(state.innerobj, needxrep, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minasasetalgorithm(minasastate state, int algotype, alglib.xparams _params)
+    {
+    
+        mincomp.minasasetalgorithm(state.innerobj, algotype, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minasasetstpmax(minasastate state, double stpmax, alglib.xparams _params)
+    {
+    
+        mincomp.minasasetstpmax(state.innerobj, stpmax, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return mincomp.minasaiteration(state.innerobj, null);
+    }
+    
+    public static bool minasaiteration(minasastate state, alglib.xparams _params)
+    {
+    
+        return mincomp.minasaiteration(state.innerobj, _params);
+    }
+    /*************************************************************************
+    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)
+    {
+        minasaoptimize(state, grad, rep, obj, null);
+    }
+    
+    public static void minasaoptimize(minasastate state, ndimensional_grad grad, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'minasaoptimize()' (grad is null)");
+        while( alglib.minasaiteration(state, _params) )
+        {
+            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, null);
+    }
+    
+    public static void minasaresults(minasastate state, out double[] x, out minasareport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new minasareport();
+        mincomp.minasaresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minasaresultsbuf(minasastate state, ref double[] x, minasareport rep, alglib.xparams _params)
+    {
+    
+        mincomp.minasaresultsbuf(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minasarestartfrom(minasastate state, double[] x, double[] bndl, double[] bndu, alglib.xparams _params)
+    {
+    
+        mincomp.minasarestartfrom(state.innerobj, x, bndl, bndu, _params);
+    }
+
+}
+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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new mincgstate((mincg.mincgstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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;
+        }
+    }
+
+
+    /*************************************************************************
+    This structure stores optimization report:
+    * IterationsCount           total number of inner iterations
+    * NFEV                      number of gradient evaluations
+    * TerminationType           termination type (see below)
+
+    TERMINATION CODES
+
+    TerminationType field contains completion code, which can be:
+      -8    internal integrity control detected  infinite  or  NAN  values  in
+            function/gradient. Abnormal termination signalled.
+       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,
+            X contains best point found so far.
+       8    terminated by user who called mincgrequesttermination(). X contains
+            point which was "current accepted" when  termination  request  was
+            submitted.
+
+    Other fields of this structure are not documented and should not be used!
+    *************************************************************************/
+    public class mincgreport : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new mincgreport((mincg.mincgreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void mincgcreate(int n, double[] x, out mincgstate state, alglib.xparams _params)
+    {
+        state = new mincgstate();
+        mincg.mincgcreate(n, x, state.innerobj, _params);
+    }
+            
+    public static void mincgcreate(double[] x, out mincgstate state)
+    {
+        int n;
+    
+        state = new mincgstate();
+        n = ap.len(x);
+        mincg.mincgcreate(n, x, state.innerobj, null);
+    
+        return;
+    }
+            
+    public static void mincgcreate(double[] x, out mincgstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new mincgstate();
+        n = ap.len(x);
+        mincg.mincgcreate(n, x, state.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void mincgcreatef(int n, double[] x, double diffstep, out mincgstate state, alglib.xparams _params)
+    {
+        state = new mincgstate();
+        mincg.mincgcreatef(n, x, diffstep, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void mincgcreatef(double[] x, double diffstep, out mincgstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new mincgstate();
+        n = ap.len(x);
+        mincg.mincgcreatef(n, x, diffstep, state.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void mincgsetcond(mincgstate state, double epsg, double epsf, double epsx, int maxits, alglib.xparams _params)
+    {
+    
+        mincg.mincgsetcond(state.innerobj, epsg, epsf, epsx, maxits, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mincgsetscale(mincgstate state, double[] s, alglib.xparams _params)
+    {
+    
+        mincg.mincgsetscale(state.innerobj, s, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mincgsetxrep(mincgstate state, bool needxrep, alglib.xparams _params)
+    {
+    
+        mincg.mincgsetxrep(state.innerobj, needxrep, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mincgsetcgtype(mincgstate state, int cgtype, alglib.xparams _params)
+    {
+    
+        mincg.mincgsetcgtype(state.innerobj, cgtype, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mincgsetstpmax(mincgstate state, double stpmax, alglib.xparams _params)
+    {
+    
+        mincg.mincgsetstpmax(state.innerobj, stpmax, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mincgsuggeststep(mincgstate state, double stp, alglib.xparams _params)
+    {
+    
+        mincg.mincgsuggeststep(state.innerobj, stp, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mincgsetprecdefault(mincgstate state, alglib.xparams _params)
+    {
+    
+        mincg.mincgsetprecdefault(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mincgsetprecdiag(mincgstate state, double[] d, alglib.xparams _params)
+    {
+    
+        mincg.mincgsetprecdiag(state.innerobj, d, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mincgsetprecscale(mincgstate state, alglib.xparams _params)
+    {
+    
+        mincg.mincgsetprecscale(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return mincg.mincgiteration(state.innerobj, null);
+    }
+    
+    public static bool mincgiteration(mincgstate state, alglib.xparams _params)
+    {
+    
+        return mincg.mincgiteration(state.innerobj, _params);
+    }
+    /*************************************************************************
+    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)
+    {
+        mincgoptimize(state, func, rep, obj, null);
+    }
+    
+    public static void mincgoptimize(mincgstate state, ndimensional_func func, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( func==null )
+            throw new alglibexception("ALGLIB: error in 'mincgoptimize()' (func is null)");
+        while( alglib.mincgiteration(state, _params) )
+        {
+            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)
+    {
+        mincgoptimize(state, grad, rep, obj, null);
+    }
+    
+    public static void mincgoptimize(mincgstate state, ndimensional_grad grad, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( grad==null )
+            throw new alglibexception("ALGLIB: error in 'mincgoptimize()' (grad is null)");
+        while( alglib.mincgiteration(state, _params) )
+        {
+            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?)");
+        }
+    }
+
+
+    
+    /*************************************************************************
+    This  function  activates/deactivates verification  of  the  user-supplied
+    analytic gradient.
+
+    Upon  activation  of  this  option  OptGuard  integrity  checker  performs
+    numerical differentiation of your target function  at  the  initial  point
+    (note: future versions may also perform check  at  the  final  point)  and
+    compares numerical gradient with analytic one provided by you.
+
+    If difference is too large, an error flag is set and optimization  session
+    continues. After optimization session is over, you can retrieve the report
+    which  stores  both  gradients  and  specific  components  highlighted  as
+    suspicious by the OptGuard.
+
+    The primary OptGuard report can be retrieved with mincgoptguardresults().
+
+    IMPORTANT: gradient check is a high-overhead option which  will  cost  you
+               about 3*N additional function evaluations. In many cases it may
+               cost as much as the rest of the optimization session.
+
+               YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO
+               CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY.
+
+    NOTE: unlike previous incarnation of the gradient checking code,  OptGuard
+          does NOT interrupt optimization even if it discovers bad gradient.
+
+    INPUT PARAMETERS:
+        State       -   structure used to store algorithm state
+        TestStep    -   verification step used for numerical differentiation:
+                        * TestStep=0 turns verification off
+                        * TestStep>0 activates verification
+                        You should carefully choose TestStep. Value  which  is
+                        too large (so large that  function  behavior  is  non-
+                        cubic at this scale) will lead  to  false  alarms. Too
+                        short step will result in rounding  errors  dominating
+                        numerical derivative.
+
+                        You may use different step for different parameters by
+                        means of setting scale with mincgsetscale().
+
+    === EXPLANATION ==========================================================
+
+    In order to verify gradient algorithm performs following steps:
+      * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i],
+        where X[i] is i-th component of the initial point and S[i] is a  scale
+        of i-th parameter
+      * F(X) is evaluated at these trial points
+      * we perform one more evaluation in the middle point of the interval
+      * we  build  cubic  model using function values and derivatives at trial
+        points and we compare its prediction with actual value in  the  middle
+        point
+
+      -- ALGLIB --
+         Copyright 15.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgoptguardgradient(mincgstate state, double teststep)
+    {
+    
+        mincg.mincgoptguardgradient(state.innerobj, teststep, null);
+    }
+    
+    public static void mincgoptguardgradient(mincgstate state, double teststep, alglib.xparams _params)
+    {
+    
+        mincg.mincgoptguardgradient(state.innerobj, teststep, _params);
+    }
+    
+    /*************************************************************************
+    This  function  activates/deactivates nonsmoothness monitoring  option  of
+    the  OptGuard  integrity  checker. Smoothness  monitor  silently  observes
+    solution process and tries to detect ill-posed problems, i.e. ones with:
+    a) discontinuous target function (non-C0)
+    b) nonsmooth     target function (non-C1)
+
+    Smoothness monitoring does NOT interrupt optimization  even if it suspects
+    that your problem is nonsmooth. It just sets corresponding  flags  in  the
+    OptGuard report which can be retrieved after optimization is over.
+
+    Smoothness monitoring is a moderate overhead option which often adds  less
+    than 1% to the optimizer running time. Thus, you can use it even for large
+    scale problems.
+
+    NOTE: OptGuard does  NOT  guarantee  that  it  will  always  detect  C0/C1
+          continuity violations.
+
+          First, minor errors are hard to  catch - say, a 0.0001 difference in
+          the model values at two sides of the gap may be due to discontinuity
+          of the model - or simply because the model has changed.
+
+          Second, C1-violations  are  especially  difficult  to  detect  in  a
+          noninvasive way. The optimizer usually  performs  very  short  steps
+          near the nonsmoothness, and differentiation  usually   introduces  a
+          lot of numerical noise.  It  is  hard  to  tell  whether  some  tiny
+          discontinuity in the slope is due to real nonsmoothness or just  due
+          to numerical noise alone.
+
+          Our top priority was to avoid false positives, so in some rare cases
+          minor errors may went unnoticed (however, in most cases they can  be
+          spotted with restart from different initial point).
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+        level   -   monitoring level:
+                    * 0 - monitoring is disabled
+                    * 1 - noninvasive low-overhead monitoring; function values
+                          and/or gradients are recorded, but OptGuard does not
+                          try to perform additional evaluations  in  order  to
+                          get more information about suspicious locations.
+
+    === EXPLANATION ==========================================================
+
+    One major source of headache during optimization  is  the  possibility  of
+    the coding errors in the target function/constraints (or their gradients).
+    Such  errors   most   often   manifest   themselves  as  discontinuity  or
+    nonsmoothness of the target/constraints.
+
+    Another frequent situation is when you try to optimize something involving
+    lots of min() and max() operations, i.e. nonsmooth target. Although not  a
+    coding error, it is nonsmoothness anyway - and smooth  optimizers  usually
+    stop right after encountering nonsmoothness, well before reaching solution.
+
+    OptGuard integrity checker helps you to catch such situations: it monitors
+    function values/gradients being passed  to  the  optimizer  and  tries  to
+    errors. Upon discovering suspicious pair of points it  raises  appropriate
+    flag (and allows you to continue optimization). When optimization is done,
+    you can study OptGuard result.
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgoptguardsmoothness(mincgstate state, int level)
+    {
+    
+        mincg.mincgoptguardsmoothness(state.innerobj, level, null);
+    }
+    
+    public static void mincgoptguardsmoothness(mincgstate state, int level, alglib.xparams _params)
+    {
+    
+        mincg.mincgoptguardsmoothness(state.innerobj, level, _params);
+    }
+            
+    public static void mincgoptguardsmoothness(mincgstate state)
+    {
+        int level;
+    
+    
+        level = 1;
+        mincg.mincgoptguardsmoothness(state.innerobj, level, null);
+    
+        return;
+    }
+            
+    public static void mincgoptguardsmoothness(mincgstate state, alglib.xparams _params)
+    {
+        int level;
+    
+    
+        level = 1;
+        mincg.mincgoptguardsmoothness(state.innerobj, level, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Results of OptGuard integrity check, should be called  after  optimization
+    session is over.
+
+    === PRIMARY REPORT =======================================================
+
+    OptGuard performs several checks which are intended to catch common errors
+    in the implementation of nonlinear function/gradient:
+    * incorrect analytic gradient
+    * discontinuous (non-C0) target functions (constraints)
+    * nonsmooth     (non-C1) target functions (constraints)
+
+    Each of these checks is activated with appropriate function:
+    * mincgoptguardgradient() for gradient verification
+    * mincgoptguardsmoothness() for C0/C1 checks
+
+    Following flags are set when these errors are suspected:
+    * rep.badgradsuspected, and additionally:
+      * rep.badgradvidx for specific variable (gradient element) suspected
+      * rep.badgradxbase, a point where gradient is tested
+      * rep.badgraduser, user-provided gradient  (stored  as  2D  matrix  with
+        single row in order to make  report  structure  compatible  with  more
+        complex optimizers like MinNLC or MinLM)
+      * rep.badgradnum,   reference    gradient    obtained    via   numerical
+        differentiation (stored as  2D matrix with single row in order to make
+        report structure compatible with more complex optimizers  like  MinNLC
+        or MinLM)
+    * rep.nonc0suspected
+    * rep.nonc1suspected
+
+    === ADDITIONAL REPORTS/LOGS ==============================================
+
+    Several different tests are performed to catch C0/C1 errors, you can  find
+    out specific test signaled error by looking to:
+    * rep.nonc0test0positive, for non-C0 test #0
+    * rep.nonc1test0positive, for non-C1 test #0
+    * rep.nonc1test1positive, for non-C1 test #1
+
+    Additional information (including line search logs)  can  be  obtained  by
+    means of:
+    * mincgoptguardnonc1test0results()
+    * mincgoptguardnonc1test1results()
+    which return detailed error reports, specific points where discontinuities
+    were found, and so on.
+
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        rep     -   generic OptGuard report;  more  detailed  reports  can  be
+                    retrieved with other functions.
+
+    NOTE: false negatives (nonsmooth problems are not identified as  nonsmooth
+          ones) are possible although unlikely.
+
+          The reason  is  that  you  need  to  make several evaluations around
+          nonsmoothness  in  order  to  accumulate  enough  information  about
+          function curvature. Say, if you start right from the nonsmooth point,
+          optimizer simply won't get enough data to understand what  is  going
+          wrong before it terminates due to abrupt changes in the  derivative.
+          It is also  possible  that  "unlucky"  step  will  move  us  to  the
+          termination too quickly.
+
+          Our current approach is to have less than 0.1%  false  negatives  in
+          our test examples  (measured  with  multiple  restarts  from  random
+          points), and to have exactly 0% false positives.
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgoptguardresults(mincgstate state, out optguardreport rep)
+    {
+        rep = new optguardreport();
+        mincg.mincgoptguardresults(state.innerobj, rep.innerobj, null);
+    }
+    
+    public static void mincgoptguardresults(mincgstate state, out optguardreport rep, alglib.xparams _params)
+    {
+        rep = new optguardreport();
+        mincg.mincgoptguardresults(state.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Detailed results of the OptGuard integrity check for nonsmoothness test #0
+
+    Nonsmoothness (non-C1) test #0 studies  function  values  (not  gradient!)
+    obtained during line searches and monitors  behavior  of  the  directional
+    derivative estimate.
+
+    This test is less powerful than test #1, but it does  not  depend  on  the
+    gradient values and thus it is more robust against artifacts introduced by
+    numerical differentiation.
+
+    Two reports are returned:
+    * a "strongest" one, corresponding  to  line   search  which  had  highest
+      value of the nonsmoothness indicator
+    * a "longest" one, corresponding to line search which  had  more  function
+      evaluations, and thus is more detailed
+
+    In both cases following fields are returned:
+
+    * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+      did not notice anything (in the latter cases fields below are empty).
+    * x0[], d[] - arrays of length N which store initial point  and  direction
+      for line search (d[] can be normalized, but does not have to)
+    * stp[], f[] - arrays of length CNT which store step lengths and  function
+      values at these points; f[i] is evaluated in x0+stp[i]*d.
+    * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+      between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+      with  most  likely  position  of  the  violation  between  stpidxa+1 and
+      stpidxa+2.
+
+    ==========================================================================
+    = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+    =                   see where C1 continuity is violated.
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        strrep  -   C1 test #0 "strong" report
+        lngrep  -   C1 test #0 "long" report
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgoptguardnonc1test0results(mincgstate state, out optguardnonc1test0report strrep, out optguardnonc1test0report lngrep)
+    {
+        strrep = new optguardnonc1test0report();
+        lngrep = new optguardnonc1test0report();
+        mincg.mincgoptguardnonc1test0results(state.innerobj, strrep.innerobj, lngrep.innerobj, null);
+    }
+    
+    public static void mincgoptguardnonc1test0results(mincgstate state, out optguardnonc1test0report strrep, out optguardnonc1test0report lngrep, alglib.xparams _params)
+    {
+        strrep = new optguardnonc1test0report();
+        lngrep = new optguardnonc1test0report();
+        mincg.mincgoptguardnonc1test0results(state.innerobj, strrep.innerobj, lngrep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Detailed results of the OptGuard integrity check for nonsmoothness test #1
+
+    Nonsmoothness (non-C1)  test  #1  studies  individual  components  of  the
+    gradient computed during line search.
+
+    When precise analytic gradient is provided this test is more powerful than
+    test #0  which  works  with  function  values  and  ignores  user-provided
+    gradient.  However,  test  #0  becomes  more   powerful   when   numerical
+    differentiation is employed (in such cases test #1 detects  higher  levels
+    of numerical noise and becomes too conservative).
+
+    This test also tells specific components of the gradient which violate  C1
+    continuity, which makes it more informative than #0, which just tells that
+    continuity is violated.
+
+    Two reports are returned:
+    * a "strongest" one, corresponding  to  line   search  which  had  highest
+      value of the nonsmoothness indicator
+    * a "longest" one, corresponding to line search which  had  more  function
+      evaluations, and thus is more detailed
+
+    In both cases following fields are returned:
+
+    * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+      did not notice anything (in the latter cases fields below are empty).
+    * vidx - is an index of the variable in [0,N) with nonsmooth derivative
+    * x0[], d[] - arrays of length N which store initial point  and  direction
+      for line search (d[] can be normalized, but does not have to)
+    * stp[], g[] - arrays of length CNT which store step lengths and  gradient
+      values at these points; g[i] is evaluated in  x0+stp[i]*d  and  contains
+      vidx-th component of the gradient.
+    * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+      between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+      with  most  likely  position  of  the  violation  between  stpidxa+1 and
+      stpidxa+2.
+
+    ==========================================================================
+    = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+    =                   see where C1 continuity is violated.
+    ==========================================================================
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        strrep  -   C1 test #1 "strong" report
+        lngrep  -   C1 test #1 "long" report
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgoptguardnonc1test1results(mincgstate state, out optguardnonc1test1report strrep, out optguardnonc1test1report lngrep)
+    {
+        strrep = new optguardnonc1test1report();
+        lngrep = new optguardnonc1test1report();
+        mincg.mincgoptguardnonc1test1results(state.innerobj, strrep.innerobj, lngrep.innerobj, null);
+    }
+    
+    public static void mincgoptguardnonc1test1results(mincgstate state, out optguardnonc1test1report strrep, out optguardnonc1test1report lngrep, alglib.xparams _params)
+    {
+        strrep = new optguardnonc1test1report();
+        lngrep = new optguardnonc1test1report();
+        mincg.mincgoptguardnonc1test1results(state.innerobj, strrep.innerobj, lngrep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Conjugate gradient results
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[0..N-1], solution
+        Rep     -   optimization report:
+                    * Rep.TerminationType completetion code:
+                        * -8    internal integrity control  detected  infinite
+                                or NAN values in  function/gradient.  Abnormal
+                                termination signalled.
+                        * -7    gradient verification failed.
+                                See MinCGSetGradientCheck() for more information.
+                        *  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, null);
+    }
+    
+    public static void mincgresults(mincgstate state, out double[] x, out mincgreport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new mincgreport();
+        mincg.mincgresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mincgresultsbuf(mincgstate state, ref double[] x, mincgreport rep, alglib.xparams _params)
+    {
+    
+        mincg.mincgresultsbuf(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void mincgrestartfrom(mincgstate state, double[] x, alglib.xparams _params)
+    {
+    
+        mincg.mincgrestartfrom(state.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine submits request for termination of running  optimizer.  It
+    should be called from user-supplied callback when user decides that it  is
+    time to "smoothly" terminate optimization process.  As  result,  optimizer
+    stops at point which was "current accepted" when termination  request  was
+    submitted and returns error code 8 (successful termination).
+
+    INPUT PARAMETERS:
+        State   -   optimizer structure
+
+    NOTE: after  request  for  termination  optimizer  may   perform   several
+          additional calls to user-supplied callbacks. It does  NOT  guarantee
+          to stop immediately - it just guarantees that these additional calls
+          will be discarded later.
+
+    NOTE: calling this function on optimizer which is NOT running will have no
+          effect.
+
+    NOTE: multiple calls to this function are possible. First call is counted,
+          subsequent calls are silently ignored.
+
+      -- ALGLIB --
+         Copyright 08.10.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void mincgrequesttermination(mincgstate state)
+    {
+    
+        mincg.mincgrequesttermination(state.innerobj, null);
+    }
+    
+    public static void mincgrequesttermination(mincgstate state, alglib.xparams _params)
+    {
+    
+        mincg.mincgrequesttermination(state.innerobj, _params);
+    }
+
+}
+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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minlmstate((minlm.minlmstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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:
+        * -8    optimizer detected NAN/INF values either in the function itself,
+                or in its Jacobian
+        * -5    inappropriate solver was used:
+                * solver created with minlmcreatefgh() used  on  problem  with
+                  general linear constraints (set with minlmsetlc() call).
+        * -3    constraints are inconsistent
+        *  2    relative step is no more than EpsX.
+        *  5    MaxIts steps was taken
+        *  7    stopping conditions are too stringent,
+                further improvement is impossible
+        *  8    terminated   by  user  who  called  MinLMRequestTermination().
+                X contains point which was "current accepted" when termination
+                request was submitted.
+    * 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new minlmreport((minlm.minlmreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void minlmcreatevj(int n, int m, double[] x, out minlmstate state, alglib.xparams _params)
+    {
+        state = new minlmstate();
+        minlm.minlmcreatevj(n, m, x, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void minlmcreatevj(int m, double[] x, out minlmstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatevj(n, m, x, state.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void minlmcreatev(int n, int m, double[] x, double diffstep, out minlmstate state, alglib.xparams _params)
+    {
+        state = new minlmstate();
+        minlm.minlmcreatev(n, m, x, diffstep, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void minlmcreatev(int m, double[] x, double diffstep, out minlmstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatev(n, m, x, diffstep, state.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void minlmcreatefgh(int n, double[] x, out minlmstate state, alglib.xparams _params)
+    {
+        state = new minlmstate();
+        minlm.minlmcreatefgh(n, x, state.innerobj, _params);
+    }
+            
+    public static void minlmcreatefgh(double[] x, out minlmstate state)
+    {
+        int n;
+    
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatefgh(n, x, state.innerobj, null);
+    
+        return;
+    }
+            
+    public static void minlmcreatefgh(double[] x, out minlmstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatefgh(n, x, state.innerobj, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function sets stopping conditions for Levenberg-Marquardt optimization
+    algorithm.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+        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()
+                    Recommended values: 1E-9 ... 1E-12.
+        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  EpsX=0  and  MaxIts=0  (simultaneously)  will  lead  to automatic
+    stopping criterion selection (small EpsX).
+
+    NOTE: it is not recommended to set large EpsX (say, 0.001). Because LM  is
+          a second-order method, it performs very precise steps anyway.
+
+      -- ALGLIB --
+         Copyright 02.04.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmsetcond(minlmstate state, double epsx, int maxits)
+    {
+    
+        minlm.minlmsetcond(state.innerobj, epsx, maxits, null);
+    }
+    
+    public static void minlmsetcond(minlmstate state, double epsx, int maxits, alglib.xparams _params)
+    {
+    
+        minlm.minlmsetcond(state.innerobj, epsx, maxits, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlmsetxrep(minlmstate state, bool needxrep, alglib.xparams _params)
+    {
+    
+        minlm.minlmsetxrep(state.innerobj, needxrep, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlmsetstpmax(minlmstate state, double stpmax, alglib.xparams _params)
+    {
+    
+        minlm.minlmsetstpmax(state.innerobj, stpmax, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlmsetscale(minlmstate state, double[] s, alglib.xparams _params)
+    {
+    
+        minlm.minlmsetscale(state.innerobj, s, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlmsetbc(minlmstate state, double[] bndl, double[] bndu, alglib.xparams _params)
+    {
+    
+        minlm.minlmsetbc(state.innerobj, bndl, bndu, _params);
+    }
+    
+    /*************************************************************************
+    This function sets general linear constraints for LM optimizer
+
+    Linear constraints are inactive by default (after initial creation).  They
+    are preserved until explicitly turned off with another minlmsetlc() call.
+
+    INPUT PARAMETERS:
+        State   -   structure stores algorithm state
+        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
+
+    IMPORTANT: if you have linear constraints, it is strongly  recommended  to
+               set scale of variables with minlmsetscale(). QP solver which is
+               used to calculate linearly constrained steps heavily relies  on
+               good scaling of input problems.
+
+    IMPORTANT: solvers created with minlmcreatefgh()  do  not  support  linear
+               constraints.
+
+    NOTE: linear  (non-bound)  constraints are satisfied only approximately  -
+          there  always  exists some violation due  to  numerical  errors  and
+          algorithmic limitations.
+
+    NOTE: general linear constraints  add  significant  overhead  to  solution
+          process. Although solver performs roughly same amount of  iterations
+          (when compared  with  similar  box-only  constrained  problem), each
+          iteration   now    involves  solution  of  linearly  constrained  QP
+          subproblem, which requires ~3-5 times more Cholesky  decompositions.
+          Thus, if you can reformulate your problem in such way  this  it  has
+          only box constraints, it may be beneficial to do so.
+
+      -- ALGLIB --
+         Copyright 14.01.2011 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmsetlc(minlmstate state, double[,] c, int[] ct, int k)
+    {
+    
+        minlm.minlmsetlc(state.innerobj, c, ct, k, null);
+    }
+    
+    public static void minlmsetlc(minlmstate state, double[,] c, int[] ct, int k, alglib.xparams _params)
+    {
+    
+        minlm.minlmsetlc(state.innerobj, c, ct, k, _params);
+    }
+            
+    public static void minlmsetlc(minlmstate state, double[,] c, int[] ct)
+    {
+        int k;
+        if( (ap.rows(c)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'minlmsetlc': looks like one of arguments has wrong size");
+    
+        k = ap.rows(c);
+        minlm.minlmsetlc(state.innerobj, c, ct, k, null);
+    
+        return;
+    }
+            
+    public static void minlmsetlc(minlmstate state, double[,] c, int[] ct, alglib.xparams _params)
+    {
+        int k;
+        if( (ap.rows(c)!=ap.len(ct)))
+            throw new alglibexception("Error while calling 'minlmsetlc': looks like one of arguments has wrong size");
+    
+        k = ap.rows(c);
+        minlm.minlmsetlc(state.innerobj, c, ct, k, _params);
+    
+        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 prohibitively
+    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         +
+
+    DEFAULT 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, null);
+    }
+    
+    public static void minlmsetacctype(minlmstate state, int acctype, alglib.xparams _params)
+    {
+    
+        minlm.minlmsetacctype(state.innerobj, acctype, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return minlm.minlmiteration(state.innerobj, null);
+    }
+    
+    public static bool minlmiteration(minlmstate state, alglib.xparams _params)
+    {
+    
+        return minlm.minlmiteration(state.innerobj, _params);
+    }
+    /*************************************************************************
+    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)
+    {
+        minlmoptimize(state, fvec, rep, obj, null);
+    }
+    
+    public static void minlmoptimize(minlmstate state, ndimensional_fvec  fvec, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        if( fvec==null )
+            throw new alglibexception("ALGLIB: error in 'minlmoptimize()' (fvec is null)");
+        while( alglib.minlmiteration(state, _params) )
+        {
+            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)
+    {
+        minlmoptimize(state, fvec, jac, rep, obj, null);
+    }
+    
+    public static void minlmoptimize(minlmstate state, ndimensional_fvec  fvec, ndimensional_jac  jac, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        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, _params) )
+        {
+            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)
+    {
+        minlmoptimize(state, func, grad, hess, rep, obj, null);
+    }
+    
+    public static void minlmoptimize(minlmstate state, ndimensional_func func, ndimensional_grad grad, ndimensional_hess hess, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        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, _params) )
+        {
+            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)
+    {
+        minlmoptimize(state, func, jac, rep, obj, null);
+    }
+    
+    public static void minlmoptimize(minlmstate state, ndimensional_func func, ndimensional_jac  jac, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        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, _params) )
+        {
+            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)
+    {
+        minlmoptimize(state, func, grad, jac, rep, obj, null);
+    }
+    
+    public static void minlmoptimize(minlmstate state, ndimensional_func func, ndimensional_grad grad, ndimensional_jac  jac, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        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, _params) )
+        {
+            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?)");
+        }
+    }
+
+
+    
+    /*************************************************************************
+    This  function  activates/deactivates verification  of  the  user-supplied
+    analytic Jacobian.
+
+    Upon  activation  of  this  option  OptGuard  integrity  checker  performs
+    numerical differentiation of your target function vector  at  the  initial
+    point (note: future versions may also perform check  at  the final  point)
+    and compares numerical Jacobian with analytic one provided by you.
+
+    If difference is too large, an error flag is set and optimization  session
+    continues. After optimization session is over, you can retrieve the report
+    which stores  both  Jacobians,  and  specific  components  highlighted  as
+    suspicious by the OptGuard.
+
+    The OptGuard report can be retrieved with minlmoptguardresults().
+
+    IMPORTANT: gradient check is a high-overhead option which  will  cost  you
+               about 3*N additional function evaluations. In many cases it may
+               cost as much as the rest of the optimization session.
+
+               YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO
+               CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY.
+
+    NOTE: unlike previous incarnation of the gradient checking code,  OptGuard
+          does NOT interrupt optimization even if it discovers bad gradient.
+
+    INPUT PARAMETERS:
+        State       -   structure used to store algorithm state
+        TestStep    -   verification step used for numerical differentiation:
+                        * TestStep=0 turns verification off
+                        * TestStep>0 activates verification
+                        You should carefully choose TestStep. Value  which  is
+                        too large (so large that  function  behavior  is  non-
+                        cubic at this scale) will lead  to  false  alarms. Too
+                        short step will result in rounding  errors  dominating
+                        numerical derivative.
+
+                        You may use different step for different parameters by
+                        means of setting scale with minlmsetscale().
+
+    === EXPLANATION ==========================================================
+
+    In order to verify gradient algorithm performs following steps:
+      * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i],
+        where X[i] is i-th component of the initial point and S[i] is a  scale
+        of i-th parameter
+      * F(X) is evaluated at these trial points
+      * we perform one more evaluation in the middle point of the interval
+      * we  build  cubic  model using function values and derivatives at trial
+        points and we compare its prediction with actual value in  the  middle
+        point
+
+      -- ALGLIB --
+         Copyright 15.06.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmoptguardgradient(minlmstate state, double teststep)
+    {
+    
+        minlm.minlmoptguardgradient(state.innerobj, teststep, null);
+    }
+    
+    public static void minlmoptguardgradient(minlmstate state, double teststep, alglib.xparams _params)
+    {
+    
+        minlm.minlmoptguardgradient(state.innerobj, teststep, _params);
+    }
+    
+    /*************************************************************************
+    Results of OptGuard integrity check, should be called  after  optimization
+    session is over.
+
+    OptGuard checks analytic Jacobian  against  reference  value  obtained  by
+    numerical differentiation with user-specified step.
+
+    NOTE: other optimizers perform additional OptGuard checks for things  like
+          C0/C1-continuity violations. However, LM optimizer  can  check  only
+          for incorrect Jacobian.
+
+          The reason is that unlike line search methods LM optimizer does  not
+          perform extensive evaluations along the line. Thus, we simply do not
+          have enough data to catch C0/C1-violations.
+
+    This check is activated with  minlmoptguardgradient() function.
+
+    Following flags are set when these errors are suspected:
+    * rep.badgradsuspected, and additionally:
+      * rep.badgradfidx for specific function (Jacobian row) suspected
+      * rep.badgradvidx for specific variable (Jacobian column) suspected
+      * rep.badgradxbase, a point where gradient/Jacobian is tested
+      * rep.badgraduser, user-provided gradient/Jacobian
+      * rep.badgradnum, reference gradient/Jacobian obtained via numerical
+        differentiation
+
+    INPUT PARAMETERS:
+        state   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        rep     -   OptGuard report
+
+      -- ALGLIB --
+         Copyright 21.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmoptguardresults(minlmstate state, out optguardreport rep)
+    {
+        rep = new optguardreport();
+        minlm.minlmoptguardresults(state.innerobj, rep.innerobj, null);
+    }
+    
+    public static void minlmoptguardresults(minlmstate state, out optguardreport rep, alglib.xparams _params)
+    {
+        rep = new optguardreport();
+        minlm.minlmoptguardresults(state.innerobj, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    Levenberg-Marquardt algorithm results
+
+    NOTE: if you activated OptGuard integrity checking functionality and  want
+          to get OptGuard report,  it  can  be  retrieved  with  the  help  of
+          minlmoptguardresults() function.
+
+    INPUT PARAMETERS:
+        State   -   algorithm state
+
+    OUTPUT PARAMETERS:
+        X       -   array[0..N-1], solution
+        Rep     -   optimization  report;  includes  termination   codes   and
+                    additional information. Termination codes are listed below,
+                    see comments for this structure for more info.
+                    Termination code is stored in rep.terminationtype field:
+                    * -8    optimizer detected NAN/INF values either in the
+                            function itself, or in its Jacobian
+                    * -3    constraints are inconsistent
+                    *  2    relative step is no more than EpsX.
+                    *  5    MaxIts steps was taken
+                    *  7    stopping conditions are too stringent,
+                            further improvement is impossible
+                    *  8    terminated by user who called minlmrequesttermination().
+                            X contains point which was "current accepted" when
+                            termination request was submitted.
+
+      -- 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, null);
+    }
+    
+    public static void minlmresults(minlmstate state, out double[] x, out minlmreport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new minlmreport();
+        minlm.minlmresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlmresultsbuf(minlmstate state, ref double[] x, minlmreport rep, alglib.xparams _params)
+    {
+    
+        minlm.minlmresultsbuf(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlmrestartfrom(minlmstate state, double[] x, alglib.xparams _params)
+    {
+    
+        minlm.minlmrestartfrom(state.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine submits request for termination of running  optimizer.  It
+    should be called from user-supplied callback when user decides that it  is
+    time to "smoothly" terminate optimization process.  As  result,  optimizer
+    stops at point which was "current accepted" when termination  request  was
+    submitted and returns error code 8 (successful termination).
+
+    INPUT PARAMETERS:
+        State   -   optimizer structure
+
+    NOTE: after  request  for  termination  optimizer  may   perform   several
+          additional calls to user-supplied callbacks. It does  NOT  guarantee
+          to stop immediately - it just guarantees that these additional calls
+          will be discarded later.
+
+    NOTE: calling this function on optimizer which is NOT running will have no
+          effect.
+
+    NOTE: multiple calls to this function are possible. First call is counted,
+          subsequent calls are silently ignored.
+
+      -- ALGLIB --
+         Copyright 08.10.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void minlmrequesttermination(minlmstate state)
+    {
+    
+        minlm.minlmrequesttermination(state.innerobj, null);
+    }
+    
+    public static void minlmrequesttermination(minlmstate state, alglib.xparams _params)
+    {
+    
+        minlm.minlmrequesttermination(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void minlmcreatevgj(int n, int m, double[] x, out minlmstate state, alglib.xparams _params)
+    {
+        state = new minlmstate();
+        minlm.minlmcreatevgj(n, m, x, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void minlmcreatevgj(int m, double[] x, out minlmstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatevgj(n, m, x, state.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void minlmcreatefgj(int n, int m, double[] x, out minlmstate state, alglib.xparams _params)
+    {
+        state = new minlmstate();
+        minlm.minlmcreatefgj(n, m, x, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void minlmcreatefgj(int m, double[] x, out minlmstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatefgj(n, m, x, state.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void minlmcreatefj(int n, int m, double[] x, out minlmstate state, alglib.xparams _params)
+    {
+        state = new minlmstate();
+        minlm.minlmcreatefj(n, m, x, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void minlmcreatefj(int m, double[] x, out minlmstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new minlmstate();
+        n = ap.len(x);
+        minlm.minlmcreatefj(n, m, x, state.innerobj, _params);
+    
+        return;
+    }
+
+}
+public partial class alglib
+{
+    public class cqmodels
+    {
+        /*************************************************************************
+        This structure describes convex quadratic model of the form:
+            f(x) = 0.5*(Alpha*x'*A*x + Tau*x'*D*x) + 0.5*Theta*(Q*x-r)'*(Q*x-r) + b'*x
+        where:
+            * Alpha>=0, Tau>=0, Theta>=0, Alpha+Tau>0.
+            * A is NxN matrix, Q is NxK matrix (N>=1, K>=0), b is Nx1 vector,
+              D is NxN diagonal matrix.
+            * "main" quadratic term Alpha*A+Lambda*D is symmetric
+              positive definite
+        Structure may contain optional equality constraints of the form x[i]=x0[i],
+        in this case functions provided by this unit calculate Newton step subject
+        to these equality constraints.
+        *************************************************************************/
+        public class convexquadraticmodel : apobject
+        {
+            public int n;
+            public int k;
+            public double alpha;
+            public double tau;
+            public double theta;
+            public double[,] a;
+            public double[,] q;
+            public double[] b;
+            public double[] r;
+            public double[] xc;
+            public double[] d;
+            public bool[] activeset;
+            public double[,] tq2dense;
+            public double[,] tk2;
+            public double[] tq2diag;
+            public double[] tq1;
+            public double[] tk1;
+            public double tq0;
+            public double tk0;
+            public double[] txc;
+            public double[] tb;
+            public int nfree;
+            public int ecakind;
+            public double[,] ecadense;
+            public double[,] eq;
+            public double[,] eccm;
+            public double[] ecadiag;
+            public double[] eb;
+            public double ec;
+            public double[] tmp0;
+            public double[] tmp1;
+            public double[] tmpg;
+            public double[,] tmp2;
+            public bool ismaintermchanged;
+            public bool issecondarytermchanged;
+            public bool islineartermchanged;
+            public bool isactivesetchanged;
+            public convexquadraticmodel()
+            {
+                init();
+            }
+            public override void init()
+            {
+                a = new double[0,0];
+                q = new double[0,0];
+                b = new double[0];
+                r = new double[0];
+                xc = new double[0];
+                d = new double[0];
+                activeset = new bool[0];
+                tq2dense = new double[0,0];
+                tk2 = new double[0,0];
+                tq2diag = new double[0];
+                tq1 = new double[0];
+                tk1 = new double[0];
+                txc = new double[0];
+                tb = new double[0];
+                ecadense = new double[0,0];
+                eq = new double[0,0];
+                eccm = new double[0,0];
+                ecadiag = new double[0];
+                eb = new double[0];
+                tmp0 = new double[0];
+                tmp1 = new double[0];
+                tmpg = new double[0];
+                tmp2 = new double[0,0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                convexquadraticmodel _result = new convexquadraticmodel();
+                _result.n = n;
+                _result.k = k;
+                _result.alpha = alpha;
+                _result.tau = tau;
+                _result.theta = theta;
+                _result.a = (double[,])a.Clone();
+                _result.q = (double[,])q.Clone();
+                _result.b = (double[])b.Clone();
+                _result.r = (double[])r.Clone();
+                _result.xc = (double[])xc.Clone();
+                _result.d = (double[])d.Clone();
+                _result.activeset = (bool[])activeset.Clone();
+                _result.tq2dense = (double[,])tq2dense.Clone();
+                _result.tk2 = (double[,])tk2.Clone();
+                _result.tq2diag = (double[])tq2diag.Clone();
+                _result.tq1 = (double[])tq1.Clone();
+                _result.tk1 = (double[])tk1.Clone();
+                _result.tq0 = tq0;
+                _result.tk0 = tk0;
+                _result.txc = (double[])txc.Clone();
+                _result.tb = (double[])tb.Clone();
+                _result.nfree = nfree;
+                _result.ecakind = ecakind;
+                _result.ecadense = (double[,])ecadense.Clone();
+                _result.eq = (double[,])eq.Clone();
+                _result.eccm = (double[,])eccm.Clone();
+                _result.ecadiag = (double[])ecadiag.Clone();
+                _result.eb = (double[])eb.Clone();
+                _result.ec = ec;
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmp1 = (double[])tmp1.Clone();
+                _result.tmpg = (double[])tmpg.Clone();
+                _result.tmp2 = (double[,])tmp2.Clone();
+                _result.ismaintermchanged = ismaintermchanged;
+                _result.issecondarytermchanged = issecondarytermchanged;
+                _result.islineartermchanged = islineartermchanged;
+                _result.isactivesetchanged = isactivesetchanged;
+                return _result;
+            }
+        };
+
+
+
+
+        public const int newtonrefinementits = 3;
+
+
+        /*************************************************************************
+        This subroutine is used to initialize CQM. By default, empty NxN model  is
+        generated, with Alpha=Lambda=Theta=0.0 and zero b.
+
+        Previously allocated buffer variables are reused as much as possible.
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqminit(int n,
+            convexquadraticmodel s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            s.n = n;
+            s.k = 0;
+            s.nfree = n;
+            s.ecakind = -1;
+            s.alpha = 0.0;
+            s.tau = 0.0;
+            s.theta = 0.0;
+            s.ismaintermchanged = true;
+            s.issecondarytermchanged = true;
+            s.islineartermchanged = true;
+            s.isactivesetchanged = true;
+            apserv.bvectorsetlengthatleast(ref s.activeset, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.xc, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.eb, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.tq1, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.txc, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.tb, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.b, s.n, _params);
+            apserv.rvectorsetlengthatleast(ref s.tk1, s.n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                s.activeset[i] = false;
+                s.xc[i] = 0.0;
+                s.b[i] = 0.0;
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine changes main quadratic term of the model.
+
+        INPUT PARAMETERS:
+            S       -   model
+            A       -   NxN matrix, only upper or lower triangle is referenced
+            IsUpper -   True, when matrix is stored in upper triangle
+            Alpha   -   multiplier; when Alpha=0, A is not referenced at all
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmseta(convexquadraticmodel s,
+            double[,] a,
+            bool isupper,
+            double alpha,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            alglib.ap.assert(math.isfinite(alpha) && (double)(alpha)>=(double)(0), "CQMSetA: Alpha<0 or is not finite number");
+            alglib.ap.assert((double)(alpha)==(double)(0) || apserv.isfinitertrmatrix(a, s.n, isupper, _params), "CQMSetA: A is not finite NxN matrix");
+            s.alpha = alpha;
+            if( (double)(alpha)>(double)(0) )
+            {
+                apserv.rmatrixsetlengthatleast(ref s.a, s.n, s.n, _params);
+                apserv.rmatrixsetlengthatleast(ref s.ecadense, s.n, s.n, _params);
+                apserv.rmatrixsetlengthatleast(ref s.tq2dense, s.n, s.n, _params);
+                for(i=0; i<=s.n-1; i++)
+                {
+                    for(j=i; j<=s.n-1; j++)
+                    {
+                        if( isupper )
+                        {
+                            v = a[i,j];
+                        }
+                        else
+                        {
+                            v = a[j,i];
+                        }
+                        s.a[i,j] = v;
+                        s.a[j,i] = v;
+                    }
+                }
+            }
+            s.ismaintermchanged = true;
+        }
+
+
+        /*************************************************************************
+        This subroutine changes main quadratic term of the model.
+
+        INPUT PARAMETERS:
+            S       -   model
+            A       -   possibly preallocated buffer
+            
+        OUTPUT PARAMETERS:
+            A       -   NxN matrix, full matrix is returned.
+                        Zero matrix is returned if model is empty.
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmgeta(convexquadraticmodel s,
+            ref double[,] a,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int n = 0;
+
+            n = s.n;
+            apserv.rmatrixsetlengthatleast(ref a, n, n, _params);
+            if( (double)(s.alpha)>(double)(0) )
+            {
+                v = s.alpha;
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        a[i,j] = v*s.a[i,j];
+                    }
+                }
+            }
+            else
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        a[i,j] = 0.0;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine rewrites diagonal of the main quadratic term of the  model
+        (dense  A)  by  vector  Z/Alpha (current value of the Alpha coefficient is
+        used).
+
+        IMPORTANT: in  case  model  has  no  dense  quadratic  term, this function
+                   allocates N*N dense matrix of zeros, and fills its diagonal  by
+                   non-zero values.
+
+        INPUT PARAMETERS:
+            S       -   model
+            Z       -   new diagonal, array[N]
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmrewritedensediagonal(convexquadraticmodel s,
+            double[] z,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+
+            n = s.n;
+            if( (double)(s.alpha)==(double)(0) )
+            {
+                apserv.rmatrixsetlengthatleast(ref s.a, s.n, s.n, _params);
+                apserv.rmatrixsetlengthatleast(ref s.ecadense, s.n, s.n, _params);
+                apserv.rmatrixsetlengthatleast(ref s.tq2dense, s.n, s.n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        s.a[i,j] = 0.0;
+                    }
+                }
+                s.alpha = 1.0;
+            }
+            for(i=0; i<=s.n-1; i++)
+            {
+                s.a[i,i] = z[i]/s.alpha;
+            }
+            s.ismaintermchanged = true;
+        }
+
+
+        /*************************************************************************
+        This subroutine changes diagonal quadratic term of the model.
+
+        INPUT PARAMETERS:
+            S       -   model
+            D       -   array[N], semidefinite diagonal matrix
+            Tau     -   multiplier; when Tau=0, D is not referenced at all
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmsetd(convexquadraticmodel s,
+            double[] d,
+            double tau,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(math.isfinite(tau) && (double)(tau)>=(double)(0), "CQMSetD: Tau<0 or is not finite number");
+            alglib.ap.assert((double)(tau)==(double)(0) || apserv.isfinitevector(d, s.n, _params), "CQMSetD: D is not finite Nx1 vector");
+            s.tau = tau;
+            if( (double)(tau)>(double)(0) )
+            {
+                apserv.rvectorsetlengthatleast(ref s.d, s.n, _params);
+                apserv.rvectorsetlengthatleast(ref s.ecadiag, s.n, _params);
+                apserv.rvectorsetlengthatleast(ref s.tq2diag, s.n, _params);
+                for(i=0; i<=s.n-1; i++)
+                {
+                    alglib.ap.assert((double)(d[i])>=(double)(0), "CQMSetD: D[i]<0");
+                    s.d[i] = d[i];
+                }
+            }
+            s.ismaintermchanged = true;
+        }
+
+
+        /*************************************************************************
+        This subroutine drops main quadratic term A from the model. It is same  as
+        call  to  CQMSetA()  with  zero  A,   but gives better performance because
+        algorithm  knows  that  matrix  is  zero  and  can  optimize    subsequent
+        calculations.
+
+        INPUT PARAMETERS:
+            S       -   model
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmdropa(convexquadraticmodel s,
+            alglib.xparams _params)
+        {
+            s.alpha = 0.0;
+            s.ismaintermchanged = true;
+        }
+
+
+        /*************************************************************************
+        This subroutine changes linear term of the model
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmsetb(convexquadraticmodel s,
+            double[] b,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(apserv.isfinitevector(b, s.n, _params), "CQMSetB: B is not finite vector");
+            apserv.rvectorsetlengthatleast(ref s.b, s.n, _params);
+            for(i=0; i<=s.n-1; i++)
+            {
+                s.b[i] = b[i];
+            }
+            s.islineartermchanged = true;
+        }
+
+
+        /*************************************************************************
+        This subroutine changes linear term of the model
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmsetq(convexquadraticmodel s,
+            double[,] q,
+            double[] r,
+            int k,
+            double theta,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(k>=0, "CQMSetQ: K<0");
+            alglib.ap.assert((k==0 || (double)(theta)==(double)(0)) || apserv.apservisfinitematrix(q, k, s.n, _params), "CQMSetQ: Q is not finite matrix");
+            alglib.ap.assert((k==0 || (double)(theta)==(double)(0)) || apserv.isfinitevector(r, k, _params), "CQMSetQ: R is not finite vector");
+            alglib.ap.assert(math.isfinite(theta) && (double)(theta)>=(double)(0), "CQMSetQ: Theta<0 or is not finite number");
+            
+            //
+            // degenerate case: K=0 or Theta=0
+            //
+            if( k==0 || (double)(theta)==(double)(0) )
+            {
+                s.k = 0;
+                s.theta = 0;
+                s.issecondarytermchanged = true;
+                return;
+            }
+            
+            //
+            // General case: both Theta>0 and K>0
+            //
+            s.k = k;
+            s.theta = theta;
+            apserv.rmatrixsetlengthatleast(ref s.q, s.k, s.n, _params);
+            apserv.rvectorsetlengthatleast(ref s.r, s.k, _params);
+            apserv.rmatrixsetlengthatleast(ref s.eq, s.k, s.n, _params);
+            apserv.rmatrixsetlengthatleast(ref s.eccm, s.k, s.k, _params);
+            apserv.rmatrixsetlengthatleast(ref s.tk2, s.k, s.n, _params);
+            for(i=0; i<=s.k-1; i++)
+            {
+                for(j=0; j<=s.n-1; j++)
+                {
+                    s.q[i,j] = q[i,j];
+                }
+                s.r[i] = r[i];
+            }
+            s.issecondarytermchanged = true;
+        }
+
+
+        /*************************************************************************
+        This subroutine changes active set
+
+        INPUT PARAMETERS
+            S       -   model
+            X       -   array[N], constraint values
+            ActiveSet-  array[N], active set. If ActiveSet[I]=True, then I-th
+                        variables is constrained to X[I].
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmsetactiveset(convexquadraticmodel s,
+            double[] x,
+            bool[] activeset,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=s.n, "CQMSetActiveSet: Length(X)<N");
+            alglib.ap.assert(alglib.ap.len(activeset)>=s.n, "CQMSetActiveSet: Length(ActiveSet)<N");
+            for(i=0; i<=s.n-1; i++)
+            {
+                s.isactivesetchanged = s.isactivesetchanged || (s.activeset[i] && !activeset[i]);
+                s.isactivesetchanged = s.isactivesetchanged || (activeset[i] && !s.activeset[i]);
+                s.activeset[i] = activeset[i];
+                if( activeset[i] )
+                {
+                    alglib.ap.assert(math.isfinite(x[i]), "CQMSetActiveSet: X[] contains infinite constraints");
+                    s.isactivesetchanged = s.isactivesetchanged || (double)(s.xc[i])!=(double)(x[i]);
+                    s.xc[i] = x[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine evaluates model at X. Active constraints are ignored.
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double cqmeval(convexquadraticmodel s,
+            double[] x,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int i_ = 0;
+
+            n = s.n;
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "CQMEval: X is not finite vector");
+            result = 0.0;
+            
+            //
+            // main quadratic term
+            //
+            if( (double)(s.alpha)>(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        result = result+s.alpha*0.5*x[i]*s.a[i,j]*x[j];
+                    }
+                }
+            }
+            if( (double)(s.tau)>(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    result = result+0.5*math.sqr(x[i])*s.tau*s.d[i];
+                }
+            }
+            
+            //
+            // secondary quadratic term
+            //
+            if( (double)(s.theta)>(double)(0) )
+            {
+                for(i=0; i<=s.k-1; i++)
+                {
+                    v = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v += s.q[i,i_]*x[i_];
+                    }
+                    result = result+0.5*s.theta*math.sqr(v-s.r[i]);
+                }
+            }
+            
+            //
+            // linear term
+            //
+            for(i=0; i<=s.n-1; i++)
+            {
+                result = result+x[i]*s.b[i];
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine evaluates model at X. Active constraints are ignored.
+        It returns:
+            R   -   model value
+            Noise-  estimate of the numerical noise in data
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmevalx(convexquadraticmodel s,
+            double[] x,
+            ref double r,
+            ref double noise,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double v2 = 0;
+            double mxq = 0;
+            double eps = 0;
+
+            r = 0;
+            noise = 0;
+
+            n = s.n;
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "CQMEval: X is not finite vector");
+            r = 0.0;
+            noise = 0.0;
+            eps = 2*math.machineepsilon;
+            mxq = 0.0;
+            
+            //
+            // Main quadratic term.
+            //
+            // Noise from the main quadratic term is equal to the
+            // maximum summand in the term.
+            //
+            if( (double)(s.alpha)>(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        v = s.alpha*0.5*x[i]*s.a[i,j]*x[j];
+                        r = r+v;
+                        noise = Math.Max(noise, eps*Math.Abs(v));
+                    }
+                }
+            }
+            if( (double)(s.tau)>(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    v = 0.5*math.sqr(x[i])*s.tau*s.d[i];
+                    r = r+v;
+                    noise = Math.Max(noise, eps*Math.Abs(v));
+                }
+            }
+            
+            //
+            // secondary quadratic term
+            //
+            // Noise from the secondary quadratic term is estimated as follows:
+            // * noise in qi*x-r[i] is estimated as
+            //   Eps*MXQ = Eps*max(|r[i]|, |q[i,j]*x[j]|)
+            // * noise in (qi*x-r[i])^2 is estimated as
+            //   NOISE = (|qi*x-r[i]|+Eps*MXQ)^2-(|qi*x-r[i]|)^2
+            //         = Eps*MXQ*(2*|qi*x-r[i]|+Eps*MXQ)
+            //
+            if( (double)(s.theta)>(double)(0) )
+            {
+                for(i=0; i<=s.k-1; i++)
+                {
+                    v = 0.0;
+                    mxq = Math.Abs(s.r[i]);
+                    for(j=0; j<=n-1; j++)
+                    {
+                        v2 = s.q[i,j]*x[j];
+                        v = v+v2;
+                        mxq = Math.Max(mxq, Math.Abs(v2));
+                    }
+                    r = r+0.5*s.theta*math.sqr(v-s.r[i]);
+                    noise = Math.Max(noise, eps*mxq*(2*Math.Abs(v-s.r[i])+eps*mxq));
+                }
+            }
+            
+            //
+            // linear term
+            //
+            for(i=0; i<=s.n-1; i++)
+            {
+                r = r+x[i]*s.b[i];
+                noise = Math.Max(noise, eps*Math.Abs(x[i]*s.b[i]));
+            }
+            
+            //
+            // Final update of the noise
+            //
+            noise = n*noise;
+        }
+
+
+        /*************************************************************************
+        This  subroutine  evaluates  gradient of the model; active constraints are
+        ignored.
+
+        INPUT PARAMETERS:
+            S       -   convex model
+            X       -   point, array[N]
+            G       -   possibly preallocated buffer; resized, if too small
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmgradunconstrained(convexquadraticmodel s,
+            double[] x,
+            ref double[] g,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int i_ = 0;
+
+            n = s.n;
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "CQMEvalGradUnconstrained: X is not finite vector");
+            apserv.rvectorsetlengthatleast(ref g, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                g[i] = 0;
+            }
+            
+            //
+            // main quadratic term
+            //
+            if( (double)(s.alpha)>(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    v = 0.0;
+                    for(j=0; j<=n-1; j++)
+                    {
+                        v = v+s.alpha*s.a[i,j]*x[j];
+                    }
+                    g[i] = g[i]+v;
+                }
+            }
+            if( (double)(s.tau)>(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    g[i] = g[i]+x[i]*s.tau*s.d[i];
+                }
+            }
+            
+            //
+            // secondary quadratic term
+            //
+            if( (double)(s.theta)>(double)(0) )
+            {
+                for(i=0; i<=s.k-1; i++)
+                {
+                    v = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v += s.q[i,i_]*x[i_];
+                    }
+                    v = s.theta*(v-s.r[i]);
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        g[i_] = g[i_] + v*s.q[i,i_];
+                    }
+                }
+            }
+            
+            //
+            // linear term
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                g[i] = g[i]+s.b[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine evaluates x'*(0.5*alpha*A+tau*D)*x
+
+        NOTE: Tmp[] must be preallocated array whose length is at least N
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double cqmxtadx2(convexquadraticmodel s,
+            double[] x,
+            double[] tmp,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int n = 0;
+            int i = 0;
+
+            n = s.n;
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "CQMXTADX2: X is not finite vector");
+            alglib.ap.assert(alglib.ap.len(tmp)>=n, "CQMXTADX2: Length(Tmp)<N");
+            result = 0.0;
+            
+            //
+            // main quadratic term
+            //
+            if( (double)(s.alpha)>(double)(0) )
+            {
+                result = result+s.alpha*0.5*ablas.rmatrixsyvmv(n, s.a, 0, 0, true, x, 0, tmp, _params);
+            }
+            if( (double)(s.tau)>(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    result = result+0.5*math.sqr(x[i])*s.tau*s.d[i];
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine evaluates (0.5*alpha*A+tau*D)*x
+
+        Y is automatically resized if needed
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmadx(convexquadraticmodel s,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+
+            n = s.n;
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "CQMEval: X is not finite vector");
+            apserv.rvectorsetlengthatleast(ref y, n, _params);
+            
+            //
+            // main quadratic term
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                y[i] = 0;
+            }
+            if( (double)(s.alpha)>(double)(0) )
+            {
+                ablas.rmatrixsymv(n, s.alpha, s.a, 0, 0, true, x, 0, 1.0, y, 0, _params);
+            }
+            if( (double)(s.tau)>(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    y[i] = y[i]+x[i]*s.tau*s.d[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine finds optimum of the model. It returns  False  on  failure
+        (indefinite/semidefinite matrix).  Optimum  is  found  subject  to  active
+        constraints.
+
+        INPUT PARAMETERS
+            S       -   model
+            X       -   possibly preallocated buffer; automatically resized, if
+                        too small enough.
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool cqmconstrainedoptimum(convexquadraticmodel s,
+            ref double[] x,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int nfree = 0;
+            int k = 0;
+            int i = 0;
+            double v = 0;
+            int cidx0 = 0;
+            int itidx = 0;
+            int i_ = 0;
+
+            
+            //
+            // Rebuild internal structures
+            //
+            if( !cqmrebuild(s, _params) )
+            {
+                result = false;
+                return result;
+            }
+            n = s.n;
+            k = s.k;
+            nfree = s.nfree;
+            result = true;
+            
+            //
+            // Calculate initial point for the iterative refinement:
+            // * free components are set to zero
+            // * constrained components are set to their constrained values
+            //
+            apserv.rvectorsetlengthatleast(ref x, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( s.activeset[i] )
+                {
+                    x[i] = s.xc[i];
+                }
+                else
+                {
+                    x[i] = 0;
+                }
+            }
+            
+            //
+            // Iterative refinement.
+            //
+            // In an ideal world without numerical errors it would be enough
+            // to make just one Newton step from initial point:
+            //   x_new = -H^(-1)*grad(x=0)
+            // However, roundoff errors can significantly deteriorate quality
+            // of the solution. So we have to recalculate gradient and to
+            // perform Newton steps several times.
+            //
+            // Below we perform fixed number of Newton iterations.
+            //
+            for(itidx=0; itidx<=newtonrefinementits-1; itidx++)
+            {
+                
+                //
+                // Calculate gradient at the current point.
+                // Move free components of the gradient in the beginning.
+                //
+                cqmgradunconstrained(s, x, ref s.tmpg, _params);
+                cidx0 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( !s.activeset[i] )
+                    {
+                        s.tmpg[cidx0] = s.tmpg[i];
+                        cidx0 = cidx0+1;
+                    }
+                }
+                
+                //
+                // Free components of the extrema are calculated in the first NFree elements of TXC.
+                //
+                // First, we have to calculate original Newton step, without rank-K perturbations
+                //
+                for(i_=0; i_<=nfree-1;i_++)
+                {
+                    s.txc[i_] = -s.tmpg[i_];
+                }
+                cqmsolveea(s, ref s.txc, ref s.tmp0, _params);
+                
+                //
+                // Then, we account for rank-K correction.
+                // Woodbury matrix identity is used.
+                //
+                if( s.k>0 && (double)(s.theta)>(double)(0) )
+                {
+                    apserv.rvectorsetlengthatleast(ref s.tmp0, Math.Max(nfree, k), _params);
+                    apserv.rvectorsetlengthatleast(ref s.tmp1, Math.Max(nfree, k), _params);
+                    for(i_=0; i_<=nfree-1;i_++)
+                    {
+                        s.tmp1[i_] = -s.tmpg[i_];
+                    }
+                    cqmsolveea(s, ref s.tmp1, ref s.tmp0, _params);
+                    for(i=0; i<=k-1; i++)
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=nfree-1;i_++)
+                        {
+                            v += s.eq[i,i_]*s.tmp1[i_];
+                        }
+                        s.tmp0[i] = v;
+                    }
+                    fbls.fblscholeskysolve(s.eccm, 1.0, k, true, s.tmp0, ref s.tmp1, _params);
+                    for(i=0; i<=nfree-1; i++)
+                    {
+                        s.tmp1[i] = 0.0;
+                    }
+                    for(i=0; i<=k-1; i++)
+                    {
+                        v = s.tmp0[i];
+                        for(i_=0; i_<=nfree-1;i_++)
+                        {
+                            s.tmp1[i_] = s.tmp1[i_] + v*s.eq[i,i_];
+                        }
+                    }
+                    cqmsolveea(s, ref s.tmp1, ref s.tmp0, _params);
+                    for(i_=0; i_<=nfree-1;i_++)
+                    {
+                        s.txc[i_] = s.txc[i_] - s.tmp1[i_];
+                    }
+                }
+                
+                //
+                // Unpack components from TXC into X. We pass through all
+                // free components of X and add our step.
+                //
+                cidx0 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( !s.activeset[i] )
+                    {
+                        x[i] = x[i]+s.txc[cidx0];
+                        cidx0 = cidx0+1;
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function scales vector  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.
+
+        This function does NOT takes into account current set of  constraints,  it
+        just performs matrix-vector multiplication  without  taking  into  account
+        constraints.
+
+        INPUT PARAMETERS:
+            S       -   model
+            X       -   vector to scale
+
+        OUTPUT PARAMETERS:
+            X       -   scaled vector
+            
+        NOTE:
+            when called for non-SPD matrices, it silently skips components of X
+            which correspond to zero or negative diagonal elements.
+            
+        NOTE:
+            this function uses diagonals of A and D; it ignores Q - rank-K term of
+            the quadratic model.
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmscalevector(convexquadraticmodel s,
+            ref double[] x,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            double v = 0;
+
+            n = s.n;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.0;
+                if( (double)(s.alpha)>(double)(0) )
+                {
+                    v = v+s.a[i,i];
+                }
+                if( (double)(s.tau)>(double)(0) )
+                {
+                    v = v+s.d[i];
+                }
+                if( (double)(v)>(double)(0) )
+                {
+                    x[i] = x[i]/v;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function returns diagonal of the A-term.
+
+        INPUT PARAMETERS:
+            S       -   model
+
+        OUTPUT PARAMETERS:
+            D       -   diagonal of the A (or zero)
+
+          -- ALGLIB --
+             Copyright 26.12.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cqmgetdiaga(convexquadraticmodel s,
+            ref double[] x,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+
+            n = s.n;
+            apserv.rvectorsetlengthatleast(ref x, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(s.alpha)>(double)(0) )
+                {
+                    x[i] = s.a[i,i];
+                }
+                else
+                {
+                    x[i] = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calls CQMRebuild() and evaluates model at X subject to
+        active constraints.
+
+        It  is  intended  for  debug  purposes only, because it evaluates model by
+        means of temporaries, which were calculated  by  CQMRebuild().  The   only
+        purpose of this function  is  to  check  correctness  of  CQMRebuild()  by
+        comparing results of this function with ones obtained by CQMEval(),  which
+        is  used  as  reference  point. The  idea is that significant deviation in
+        results  of  these  two  functions  is  evidence  of  some  error  in  the
+        CQMRebuild().
+
+        NOTE: suffix T denotes that temporaries marked by T-prefix are used. There
+              is one more variant of this function, which uses  "effective"  model
+              built by CQMRebuild().
+
+        NOTE2: in case CQMRebuild() fails (due to model non-convexity), this
+              function returns NAN.
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double cqmdebugconstrainedevalt(convexquadraticmodel s,
+            double[] x,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int n = 0;
+            int nfree = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            n = s.n;
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "CQMDebugConstrainedEvalT: X is not finite vector");
+            if( !cqmrebuild(s, _params) )
+            {
+                result = Double.NaN;
+                return result;
+            }
+            result = 0.0;
+            nfree = s.nfree;
+            
+            //
+            // Reorder variables
+            //
+            j = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( !s.activeset[i] )
+                {
+                    alglib.ap.assert(j<nfree, "CQMDebugConstrainedEvalT: internal error");
+                    s.txc[j] = x[i];
+                    j = j+1;
+                }
+            }
+            
+            //
+            // TQ2, TQ1, TQ0
+            //
+            //
+            if( (double)(s.alpha)>(double)(0) )
+            {
+                
+                //
+                // Dense TQ2
+                //
+                for(i=0; i<=nfree-1; i++)
+                {
+                    for(j=0; j<=nfree-1; j++)
+                    {
+                        result = result+0.5*s.txc[i]*s.tq2dense[i,j]*s.txc[j];
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Diagonal TQ2
+                //
+                for(i=0; i<=nfree-1; i++)
+                {
+                    result = result+0.5*s.tq2diag[i]*math.sqr(s.txc[i]);
+                }
+            }
+            for(i=0; i<=nfree-1; i++)
+            {
+                result = result+s.tq1[i]*s.txc[i];
+            }
+            result = result+s.tq0;
+            
+            //
+            // TK2, TK1, TK0
+            //
+            if( s.k>0 && (double)(s.theta)>(double)(0) )
+            {
+                for(i=0; i<=s.k-1; i++)
+                {
+                    v = 0;
+                    for(j=0; j<=nfree-1; j++)
+                    {
+                        v = v+s.tk2[i,j]*s.txc[j];
+                    }
+                    result = result+0.5*math.sqr(v);
+                }
+                for(i=0; i<=nfree-1; i++)
+                {
+                    result = result+s.tk1[i]*s.txc[i];
+                }
+                result = result+s.tk0;
+            }
+            
+            //
+            // TB (Bf and Bc parts)
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                result = result+s.tb[i]*s.txc[i];
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine calls CQMRebuild() and evaluates model at X subject to
+        active constraints.
+
+        It  is  intended  for  debug  purposes only, because it evaluates model by
+        means of "effective" matrices built by CQMRebuild(). The only  purpose  of
+        this function is to check correctness of CQMRebuild() by comparing results
+        of this function with  ones  obtained  by  CQMEval(),  which  is  used  as
+        reference  point.  The  idea  is  that significant deviation in results of
+        these two functions is evidence of some error in the CQMRebuild().
+
+        NOTE: suffix E denotes that effective matrices. There is one more  variant
+              of this function, which uses temporary matrices built by
+              CQMRebuild().
+
+        NOTE2: in case CQMRebuild() fails (due to model non-convexity), this
+              function returns NAN.
+
+          -- ALGLIB --
+             Copyright 12.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double cqmdebugconstrainedevale(convexquadraticmodel s,
+            double[] x,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int n = 0;
+            int nfree = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            n = s.n;
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "CQMDebugConstrainedEvalE: X is not finite vector");
+            if( !cqmrebuild(s, _params) )
+            {
+                result = Double.NaN;
+                return result;
+            }
+            result = 0.0;
+            nfree = s.nfree;
+            
+            //
+            // Reorder variables
+            //
+            j = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( !s.activeset[i] )
+                {
+                    alglib.ap.assert(j<nfree, "CQMDebugConstrainedEvalE: internal error");
+                    s.txc[j] = x[i];
+                    j = j+1;
+                }
+            }
+            
+            //
+            // ECA
+            //
+            alglib.ap.assert((s.ecakind==0 || s.ecakind==1) || (s.ecakind==-1 && nfree==0), "CQMDebugConstrainedEvalE: unexpected ECAKind");
+            if( s.ecakind==0 )
+            {
+                
+                //
+                // Dense ECA
+                //
+                for(i=0; i<=nfree-1; i++)
+                {
+                    v = 0.0;
+                    for(j=i; j<=nfree-1; j++)
+                    {
+                        v = v+s.ecadense[i,j]*s.txc[j];
+                    }
+                    result = result+0.5*math.sqr(v);
+                }
+            }
+            if( s.ecakind==1 )
+            {
+                
+                //
+                // Diagonal ECA
+                //
+                for(i=0; i<=nfree-1; i++)
+                {
+                    result = result+0.5*math.sqr(s.ecadiag[i]*s.txc[i]);
+                }
+            }
+            
+            //
+            // EQ
+            //
+            for(i=0; i<=s.k-1; i++)
+            {
+                v = 0.0;
+                for(j=0; j<=nfree-1; j++)
+                {
+                    v = v+s.eq[i,j]*s.txc[j];
+                }
+                result = result+0.5*math.sqr(v);
+            }
+            
+            //
+            // EB
+            //
+            for(i=0; i<=nfree-1; i++)
+            {
+                result = result+s.eb[i]*s.txc[i];
+            }
+            
+            //
+            // EC
+            //
+            result = result+s.ec;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal function, rebuilds "effective" model subject to constraints.
+        Returns False on failure (non-SPD main quadratic term)
+
+          -- ALGLIB --
+             Copyright 10.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool cqmrebuild(convexquadraticmodel s,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int nfree = 0;
+            int k = 0;
+            int i = 0;
+            int j = 0;
+            int ridx0 = 0;
+            int ridx1 = 0;
+            int cidx0 = 0;
+            int cidx1 = 0;
+            double v = 0;
+            int i_ = 0;
+
+            if( (double)(s.alpha)==(double)(0) && (double)(s.tau)==(double)(0) )
+            {
+                
+                //
+                // Non-SPD model, quick exit
+                //
+                result = false;
+                return result;
+            }
+            result = true;
+            n = s.n;
+            k = s.k;
+            
+            //
+            // Determine number of free variables.
+            // Fill TXC - array whose last N-NFree elements store constraints.
+            //
+            if( s.isactivesetchanged )
+            {
+                s.nfree = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( !s.activeset[i] )
+                    {
+                        s.nfree = s.nfree+1;
+                    }
+                }
+                j = s.nfree;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( s.activeset[i] )
+                    {
+                        s.txc[j] = s.xc[i];
+                        j = j+1;
+                    }
+                }
+            }
+            nfree = s.nfree;
+            
+            //
+            // Re-evaluate TQ2/TQ1/TQ0, if needed
+            //
+            if( s.isactivesetchanged || s.ismaintermchanged )
+            {
+                
+                //
+                // Handle cases Alpha>0 and Alpha=0 separately:
+                // * in the first case we have dense matrix
+                // * in the second one we have diagonal matrix, which can be
+                //   handled more efficiently
+                //
+                if( (double)(s.alpha)>(double)(0) )
+                {
+                    
+                    //
+                    // Alpha>0, dense QP
+                    //
+                    // Split variables into two groups - free (F) and constrained (C). Reorder
+                    // variables in such way that free vars come first, constrained are last:
+                    // x = [xf, xc].
+                    // 
+                    // Main quadratic term x'*(alpha*A+tau*D)*x now splits into quadratic part,
+                    // linear part and constant part:
+                    //                   ( alpha*Aff+tau*Df  alpha*Afc        ) ( xf )              
+                    //   0.5*( xf' xc' )*(                                    )*(    ) =
+                    //                   ( alpha*Acf         alpha*Acc+tau*Dc ) ( xc )
+                    //
+                    //   = 0.5*xf'*(alpha*Aff+tau*Df)*xf + (alpha*Afc*xc)'*xf + 0.5*xc'(alpha*Acc+tau*Dc)*xc
+                    //                    
+                    // We store these parts into temporary variables:
+                    // * alpha*Aff+tau*Df, alpha*Afc, alpha*Acc+tau*Dc are stored into upper
+                    //   triangle of TQ2
+                    // * alpha*Afc*xc is stored into TQ1
+                    // * 0.5*xc'(alpha*Acc+tau*Dc)*xc is stored into TQ0
+                    //
+                    // Below comes first part of the work - generation of TQ2:
+                    // * we pass through rows of A and copy I-th row into upper block (Aff/Afc) or
+                    //   lower one (Acf/Acc) of TQ2, depending on presence of X[i] in the active set.
+                    //   RIdx0 variable contains current position for insertion into upper block,
+                    //   RIdx1 contains current position for insertion into lower one.
+                    // * within each row, we copy J-th element into left half (Aff/Acf) or right
+                    //   one (Afc/Acc), depending on presence of X[j] in the active set. CIdx0
+                    //   contains current position for insertion into left block, CIdx1 contains
+                    //   position for insertion into right one.
+                    // * during copying, we multiply elements by alpha and add diagonal matrix D.
+                    //
+                    ridx0 = 0;
+                    ridx1 = s.nfree;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        cidx0 = 0;
+                        cidx1 = s.nfree;
+                        for(j=0; j<=n-1; j++)
+                        {
+                            if( !s.activeset[i] && !s.activeset[j] )
+                            {
+                                
+                                //
+                                // Element belongs to Aff
+                                //
+                                v = s.alpha*s.a[i,j];
+                                if( i==j && (double)(s.tau)>(double)(0) )
+                                {
+                                    v = v+s.tau*s.d[i];
+                                }
+                                s.tq2dense[ridx0,cidx0] = v;
+                            }
+                            if( !s.activeset[i] && s.activeset[j] )
+                            {
+                                
+                                //
+                                // Element belongs to Afc
+                                //
+                                s.tq2dense[ridx0,cidx1] = s.alpha*s.a[i,j];
+                            }
+                            if( s.activeset[i] && !s.activeset[j] )
+                            {
+                                
+                                //
+                                // Element belongs to Acf
+                                //
+                                s.tq2dense[ridx1,cidx0] = s.alpha*s.a[i,j];
+                            }
+                            if( s.activeset[i] && s.activeset[j] )
+                            {
+                                
+                                //
+                                // Element belongs to Acc
+                                //
+                                v = s.alpha*s.a[i,j];
+                                if( i==j && (double)(s.tau)>(double)(0) )
+                                {
+                                    v = v+s.tau*s.d[i];
+                                }
+                                s.tq2dense[ridx1,cidx1] = v;
+                            }
+                            if( s.activeset[j] )
+                            {
+                                cidx1 = cidx1+1;
+                            }
+                            else
+                            {
+                                cidx0 = cidx0+1;
+                            }
+                        }
+                        if( s.activeset[i] )
+                        {
+                            ridx1 = ridx1+1;
+                        }
+                        else
+                        {
+                            ridx0 = ridx0+1;
+                        }
+                    }
+                    
+                    //
+                    // Now we have TQ2, and we can evaluate TQ1.
+                    // In the special case when we have Alpha=0, NFree=0 or NFree=N,
+                    // TQ1 is filled by zeros.
+                    //
+                    for(i=0; i<=n-1; i++)
+                    {
+                        s.tq1[i] = 0.0;
+                    }
+                    if( s.nfree>0 && s.nfree<n )
+                    {
+                        ablas.rmatrixmv(s.nfree, n-s.nfree, s.tq2dense, 0, s.nfree, 0, s.txc, s.nfree, s.tq1, 0, _params);
+                    }
+                    
+                    //
+                    // And finally, we evaluate TQ0.
+                    //
+                    v = 0.0;
+                    for(i=s.nfree; i<=n-1; i++)
+                    {
+                        for(j=s.nfree; j<=n-1; j++)
+                        {
+                            v = v+0.5*s.txc[i]*s.tq2dense[i,j]*s.txc[j];
+                        }
+                    }
+                    s.tq0 = v;
+                }
+                else
+                {
+                    
+                    //
+                    // Alpha=0, diagonal QP
+                    //
+                    // Split variables into two groups - free (F) and constrained (C). Reorder
+                    // variables in such way that free vars come first, constrained are last:
+                    // x = [xf, xc].
+                    // 
+                    // Main quadratic term x'*(tau*D)*x now splits into quadratic and constant
+                    // parts:
+                    //                   ( tau*Df        ) ( xf )              
+                    //   0.5*( xf' xc' )*(               )*(    ) =
+                    //                   (        tau*Dc ) ( xc )
+                    //
+                    //   = 0.5*xf'*(tau*Df)*xf + 0.5*xc'(tau*Dc)*xc
+                    //                    
+                    // We store these parts into temporary variables:
+                    // * tau*Df is stored in TQ2Diag
+                    // * 0.5*xc'(tau*Dc)*xc is stored into TQ0
+                    //
+                    s.tq0 = 0.0;
+                    ridx0 = 0;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        if( !s.activeset[i] )
+                        {
+                            s.tq2diag[ridx0] = s.tau*s.d[i];
+                            ridx0 = ridx0+1;
+                        }
+                        else
+                        {
+                            s.tq0 = s.tq0+0.5*s.tau*s.d[i]*math.sqr(s.xc[i]);
+                        }
+                    }
+                    for(i=0; i<=n-1; i++)
+                    {
+                        s.tq1[i] = 0.0;
+                    }
+                }
+            }
+            
+            //
+            // Re-evaluate TK2/TK1/TK0, if needed
+            //
+            if( s.isactivesetchanged || s.issecondarytermchanged )
+            {
+                
+                //
+                // Split variables into two groups - free (F) and constrained (C). Reorder
+                // variables in such way that free vars come first, constrained are last:
+                // x = [xf, xc].
+                // 
+                // Secondary term theta*(Q*x-r)'*(Q*x-r) now splits into quadratic part,
+                // linear part and constant part:
+                //             (          ( xf )     )'  (          ( xf )     )
+                //   0.5*theta*( (Qf Qc)'*(    ) - r ) * ( (Qf Qc)'*(    ) - r ) =
+                //             (          ( xc )     )   (          ( xc )     )
+                //
+                //   = 0.5*theta*xf'*(Qf'*Qf)*xf + theta*((Qc*xc-r)'*Qf)*xf + 
+                //     + theta*(-r'*(Qc*xc-r)-0.5*r'*r+0.5*xc'*Qc'*Qc*xc)
+                //                    
+                // We store these parts into temporary variables:
+                // * sqrt(theta)*Qf is stored into TK2
+                // * theta*((Qc*xc-r)'*Qf) is stored into TK1
+                // * theta*(-r'*(Qc*xc-r)-0.5*r'*r+0.5*xc'*Qc'*Qc*xc) is stored into TK0
+                //
+                // We use several other temporaries to store intermediate results:
+                // * Tmp0 - to store Qc*xc-r
+                // * Tmp1 - to store Qc*xc
+                //
+                // Generation of TK2/TK1/TK0 is performed as follows:
+                // * we fill TK2/TK1/TK0 (to handle K=0 or Theta=0)
+                // * other steps are performed only for K>0 and Theta>0
+                // * we pass through columns of Q and copy I-th column into left block (Qf) or
+                //   right one (Qc) of TK2, depending on presence of X[i] in the active set.
+                //   CIdx0 variable contains current position for insertion into upper block,
+                //   CIdx1 contains current position for insertion into lower one.
+                // * we calculate Qc*xc-r and store it into Tmp0
+                // * we calculate TK0 and TK1
+                // * we multiply leading part of TK2 which stores Qf by sqrt(theta)
+                //   it is important to perform this step AFTER calculation of TK0 and TK1,
+                //   because we need original (non-modified) Qf to calculate TK0 and TK1.
+                //
+                for(j=0; j<=n-1; j++)
+                {
+                    for(i=0; i<=k-1; i++)
+                    {
+                        s.tk2[i,j] = 0.0;
+                    }
+                    s.tk1[j] = 0.0;
+                }
+                s.tk0 = 0.0;
+                if( s.k>0 && (double)(s.theta)>(double)(0) )
+                {
+                    
+                    //
+                    // Split Q into Qf and Qc
+                    // Calculate Qc*xc-r, store in Tmp0
+                    //
+                    apserv.rvectorsetlengthatleast(ref s.tmp0, k, _params);
+                    apserv.rvectorsetlengthatleast(ref s.tmp1, k, _params);
+                    cidx0 = 0;
+                    cidx1 = nfree;
+                    for(i=0; i<=k-1; i++)
+                    {
+                        s.tmp1[i] = 0.0;
+                    }
+                    for(j=0; j<=n-1; j++)
+                    {
+                        if( s.activeset[j] )
+                        {
+                            for(i=0; i<=k-1; i++)
+                            {
+                                s.tk2[i,cidx1] = s.q[i,j];
+                                s.tmp1[i] = s.tmp1[i]+s.q[i,j]*s.txc[cidx1];
+                            }
+                            cidx1 = cidx1+1;
+                        }
+                        else
+                        {
+                            for(i=0; i<=k-1; i++)
+                            {
+                                s.tk2[i,cidx0] = s.q[i,j];
+                            }
+                            cidx0 = cidx0+1;
+                        }
+                    }
+                    for(i=0; i<=k-1; i++)
+                    {
+                        s.tmp0[i] = s.tmp1[i]-s.r[i];
+                    }
+                    
+                    //
+                    // Calculate TK0
+                    //
+                    v = 0.0;
+                    for(i=0; i<=k-1; i++)
+                    {
+                        v = v+s.theta*(0.5*math.sqr(s.tmp1[i])-s.r[i]*s.tmp0[i]-0.5*math.sqr(s.r[i]));
+                    }
+                    s.tk0 = v;
+                    
+                    //
+                    // Calculate TK1
+                    //
+                    if( nfree>0 )
+                    {
+                        for(i=0; i<=k-1; i++)
+                        {
+                            v = s.theta*s.tmp0[i];
+                            for(i_=0; i_<=nfree-1;i_++)
+                            {
+                                s.tk1[i_] = s.tk1[i_] + v*s.tk2[i,i_];
+                            }
+                        }
+                    }
+                    
+                    //
+                    // Calculate TK2
+                    //
+                    if( nfree>0 )
+                    {
+                        v = Math.Sqrt(s.theta);
+                        for(i=0; i<=k-1; i++)
+                        {
+                            for(i_=0; i_<=nfree-1;i_++)
+                            {
+                                s.tk2[i,i_] = v*s.tk2[i,i_];
+                            }
+                        }
+                    }
+                }
+            }
+            
+            //
+            // Re-evaluate TB
+            //
+            if( s.isactivesetchanged || s.islineartermchanged )
+            {
+                ridx0 = 0;
+                ridx1 = nfree;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( s.activeset[i] )
+                    {
+                        s.tb[ridx1] = s.b[i];
+                        ridx1 = ridx1+1;
+                    }
+                    else
+                    {
+                        s.tb[ridx0] = s.b[i];
+                        ridx0 = ridx0+1;
+                    }
+                }
+            }
+            
+            //
+            // Compose ECA: either dense ECA or diagonal ECA
+            //
+            if( (s.isactivesetchanged || s.ismaintermchanged) && nfree>0 )
+            {
+                if( (double)(s.alpha)>(double)(0) )
+                {
+                    
+                    //
+                    // Dense ECA
+                    //
+                    s.ecakind = 0;
+                    for(i=0; i<=nfree-1; i++)
+                    {
+                        for(j=i; j<=nfree-1; j++)
+                        {
+                            s.ecadense[i,j] = s.tq2dense[i,j];
+                        }
+                    }
+                    if( !trfac.spdmatrixcholeskyrec(ref s.ecadense, 0, nfree, true, ref s.tmp0, _params) )
+                    {
+                        result = false;
+                        return result;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Diagonal ECA
+                    //
+                    s.ecakind = 1;
+                    for(i=0; i<=nfree-1; i++)
+                    {
+                        if( (double)(s.tq2diag[i])<(double)(0) )
+                        {
+                            result = false;
+                            return result;
+                        }
+                        s.ecadiag[i] = Math.Sqrt(s.tq2diag[i]);
+                    }
+                }
+            }
+            
+            //
+            // Compose EQ
+            //
+            if( s.isactivesetchanged || s.issecondarytermchanged )
+            {
+                for(i=0; i<=k-1; i++)
+                {
+                    for(j=0; j<=nfree-1; j++)
+                    {
+                        s.eq[i,j] = s.tk2[i,j];
+                    }
+                }
+            }
+            
+            //
+            // Calculate ECCM
+            //
+            if( ((((s.isactivesetchanged || s.ismaintermchanged) || s.issecondarytermchanged) && s.k>0) && (double)(s.theta)>(double)(0)) && nfree>0 )
+            {
+                
+                //
+                // Calculate ECCM - Cholesky factor of the "effective" capacitance
+                // matrix CM = I + EQ*inv(EffectiveA)*EQ'.
+                //
+                // We calculate CM as follows:
+                //   CM = I + EQ*inv(EffectiveA)*EQ'
+                //      = I + EQ*ECA^(-1)*ECA^(-T)*EQ'
+                //      = I + (EQ*ECA^(-1))*(EQ*ECA^(-1))'
+                //
+                // Then we perform Cholesky decomposition of CM.
+                //
+                apserv.rmatrixsetlengthatleast(ref s.tmp2, k, n, _params);
+                ablas.rmatrixcopy(k, nfree, s.eq, 0, 0, s.tmp2, 0, 0, _params);
+                alglib.ap.assert(s.ecakind==0 || s.ecakind==1, "CQMRebuild: unexpected ECAKind");
+                if( s.ecakind==0 )
+                {
+                    ablas.rmatrixrighttrsm(k, nfree, s.ecadense, 0, 0, true, false, 0, s.tmp2, 0, 0, _params);
+                }
+                if( s.ecakind==1 )
+                {
+                    for(i=0; i<=k-1; i++)
+                    {
+                        for(j=0; j<=nfree-1; j++)
+                        {
+                            s.tmp2[i,j] = s.tmp2[i,j]/s.ecadiag[j];
+                        }
+                    }
+                }
+                for(i=0; i<=k-1; i++)
+                {
+                    for(j=0; j<=k-1; j++)
+                    {
+                        s.eccm[i,j] = 0.0;
+                    }
+                    s.eccm[i,i] = 1.0;
+                }
+                ablas.rmatrixsyrk(k, nfree, 1.0, s.tmp2, 0, 0, 0, 1.0, s.eccm, 0, 0, true, _params);
+                if( !trfac.spdmatrixcholeskyrec(ref s.eccm, 0, k, true, ref s.tmp0, _params) )
+                {
+                    result = false;
+                    return result;
+                }
+            }
+            
+            //
+            // Compose EB and EC
+            //
+            // NOTE: because these quantities are cheap to compute, we do not
+            // use caching here.
+            //
+            for(i=0; i<=nfree-1; i++)
+            {
+                s.eb[i] = s.tq1[i]+s.tk1[i]+s.tb[i];
+            }
+            s.ec = s.tq0+s.tk0;
+            for(i=nfree; i<=n-1; i++)
+            {
+                s.ec = s.ec+s.tb[i]*s.txc[i];
+            }
+            
+            //
+            // Change cache status - everything is cached 
+            //
+            s.ismaintermchanged = false;
+            s.issecondarytermchanged = false;
+            s.islineartermchanged = false;
+            s.isactivesetchanged = false;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Internal function, solves system Effective_A*x = b.
+        It should be called after successful completion of CQMRebuild().
+
+        INPUT PARAMETERS:
+            S       -   quadratic model, after call to CQMRebuild()
+            X       -   right part B, array[S.NFree]
+            Tmp     -   temporary array, automatically reallocated if needed
+
+        OUTPUT PARAMETERS:
+            X       -   solution, array[S.NFree]
+            
+        NOTE: when called with zero S.NFree, returns silently
+        NOTE: this function assumes that EA is non-degenerate
+
+          -- ALGLIB --
+             Copyright 10.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        private static void cqmsolveea(convexquadraticmodel s,
+            ref double[] x,
+            ref double[] tmp,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert((s.ecakind==0 || s.ecakind==1) || (s.ecakind==-1 && s.nfree==0), "CQMSolveEA: unexpected ECAKind");
+            if( s.ecakind==0 )
+            {
+                
+                //
+                // Dense ECA, use FBLSCholeskySolve() dense solver.
+                //
+                fbls.fblscholeskysolve(s.ecadense, 1.0, s.nfree, true, x, ref tmp, _params);
+            }
+            if( s.ecakind==1 )
+            {
+                
+                //
+                // Diagonal ECA
+                //
+                for(i=0; i<=s.nfree-1; i++)
+                {
+                    x[i] = x[i]/math.sqr(s.ecadiag[i]);
+                }
+            }
+        }
+
+
+    }
+    public class optguardapi
+    {
+        /*************************************************************************
+        This structure is used to store  OptGuard  report,  i.e.  report  on   the
+        properties of the nonlinear function being optimized with ALGLIB.
+
+        After you tell your optimizer to activate OptGuard  this technology starts
+        to silently monitor function values and gradients/Jacobians  being  passed
+        all around during your optimization session. Depending on specific set  of
+        checks enabled OptGuard may perform additional function evaluations  (say,
+        about 3*N evaluations if you want to check analytic gradient for errors).
+
+        Upon discovering that something strange happens  (function  values  and/or
+        gradient components change too sharply and/or unexpectedly) OptGuard  sets
+        one of the "suspicion  flags" (without interrupting optimization session).
+        After optimization is done, you can examine OptGuard report.
+
+        Following report fields can be set:
+        * nonc0suspected
+        * nonc1suspected
+        * badgradsuspected
+
+
+        === WHAT CAN BE DETECTED WITH OptGuard INTEGRITY CHECKER =================
+
+        Following  types  of  errors  in your target function (constraints) can be
+        caught:
+        a) discontinuous functions ("non-C0" part of the report)
+        b) functions with discontinuous derivative ("non-C1" part of the report)
+        c) errors in the analytic gradient provided by user
+
+        These types of errors result in optimizer  stopping  well  before reaching
+        solution (most often - right after encountering discontinuity).
+
+        Type A errors are usually  coding  errors  during  implementation  of  the
+        target function. Most "normal" problems involve continuous functions,  and
+        anyway you can't reliably optimize discontinuous function.
+
+        Type B errors are either coding errors or (in case code itself is correct)
+        evidence of the fact  that  your  problem  is  an  "incorrect"  one.  Most
+        optimizers (except for ones provided by MINNS subpackage) do  not  support
+        nonsmooth problems.
+
+        Type C errors are coding errors which often prevent optimizer from  making
+        even one step  or result in optimizing stopping  too  early,  as  soon  as
+        actual descent direction becomes too different from one suggested by user-
+        supplied gradient.
+
+
+        === WHAT IS REPORTED =====================================================
+
+        Following set of report fields deals with discontinuous  target functions,
+        ones not belonging to C0 continuity class:
+
+        * nonc0suspected - is a flag which is set upon discovering some indication
+          of the discontinuity. If this flag is false, the rest of "non-C0" fields
+          should be ignored
+        * nonc0fidx - is an index of the function (0 for  target  function,  1  or
+          higher for nonlinear constraints) which is suspected of being "non-C0"
+        * nonc0lipshitzc - a Lipchitz constant for a function which was  suspected
+          of being non-continuous.
+        * nonc0test0positive -  set  to  indicate  specific  test  which  detected
+          continuity violation (test #0)
+
+        Following set of report fields deals with discontinuous gradient/Jacobian,
+        i.e. with functions violating C1 continuity:
+
+        * nonc1suspected - is a flag which is set upon discovering some indication
+          of the discontinuity. If this flag is false, the rest of "non-C1" fields
+          should be ignored
+        * nonc1fidx - is an index of the function (0 for  target  function,  1  or
+          higher for nonlinear constraints) which is suspected of being "non-C1"
+        * nonc1lipshitzc - a Lipchitz constant for a function gradient  which  was
+          suspected of being non-smooth.
+        * nonc1test0positive -  set  to  indicate  specific  test  which  detected
+          continuity violation (test #0)
+        * nonc1test1positive -  set  to  indicate  specific  test  which  detected
+          continuity violation (test #1)
+
+        Following set of report fields deals with errors in the gradient:
+        * badgradsuspected - is a flad which is set upon discovering an  error  in
+          the analytic gradient supplied by user
+        * badgradfidx - index  of   the  function  with bad gradient (0 for target
+          function, 1 or higher for nonlinear constraints)
+        * badgradvidx - index of the variable
+        * badgradxbase - location where Jacobian is tested
+        * following  matrices  store  user-supplied  Jacobian  and  its  numerical
+          differentiation version (which is assumed to be  free  from  the  coding
+          errors), both of them computed near the initial point:
+          * badgraduser, an array[K,N], analytic Jacobian supplied by user
+          * badgradnum,  an array[K,N], numeric  Jacobian computed by ALGLIB
+          Here K is a total number of  nonlinear  functions  (target  +  nonlinear
+          constraints), N is a variable number.
+          The  element  of  badgraduser[] with index [badgradfidx,badgradvidx]  is
+          assumed to be wrong.
+
+        More detailed error log can  be  obtained  from  optimizer  by  explicitly
+        requesting reports for tests C0.0, C1.0, C1.1.
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public class optguardreport : apobject
+        {
+            public bool nonc0suspected;
+            public bool nonc0test0positive;
+            public int nonc0fidx;
+            public double nonc0lipschitzc;
+            public bool nonc1suspected;
+            public bool nonc1test0positive;
+            public bool nonc1test1positive;
+            public int nonc1fidx;
+            public double nonc1lipschitzc;
+            public bool badgradsuspected;
+            public int badgradfidx;
+            public int badgradvidx;
+            public double[] badgradxbase;
+            public double[,] badgraduser;
+            public double[,] badgradnum;
+            public optguardreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+                badgradxbase = new double[0];
+                badgraduser = new double[0,0];
+                badgradnum = new double[0,0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                optguardreport _result = new optguardreport();
+                _result.nonc0suspected = nonc0suspected;
+                _result.nonc0test0positive = nonc0test0positive;
+                _result.nonc0fidx = nonc0fidx;
+                _result.nonc0lipschitzc = nonc0lipschitzc;
+                _result.nonc1suspected = nonc1suspected;
+                _result.nonc1test0positive = nonc1test0positive;
+                _result.nonc1test1positive = nonc1test1positive;
+                _result.nonc1fidx = nonc1fidx;
+                _result.nonc1lipschitzc = nonc1lipschitzc;
+                _result.badgradsuspected = badgradsuspected;
+                _result.badgradfidx = badgradfidx;
+                _result.badgradvidx = badgradvidx;
+                _result.badgradxbase = (double[])badgradxbase.Clone();
+                _result.badgraduser = (double[,])badgraduser.Clone();
+                _result.badgradnum = (double[,])badgradnum.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This  structure  is  used  for  detailed   reporting  about  suspected  C1
+        continuity violation as flagged by C1 test #0 (OptGuard  has several tests
+        for C1 continuity, this report is used by #0).
+
+        === WHAT IS TESTED =======================================================
+
+        C1 test #0 studies function values (not gradient!)  obtained  during  line
+        searches and monitors behavior of directional  derivative  estimate.  This
+        test is less powerful than test #1, but it does  not  depend  on  gradient
+        values  and  thus  it  is  more  robust  against  artifacts  introduced by
+        numerical differentiation.
+
+
+        === WHAT IS REPORTED =====================================================
+
+        Actually, report retrieval function returns TWO report structures:
+
+        * one for most suspicious point found so far (one with highest  change  in
+          the directional derivative), so called "strongest" report
+        * another one for most detailed line search (more function  evaluations  =
+          easier to understand what's going on) which triggered  test #0 criteria,
+          so called "longest" report
+
+        In both cases following fields are returned:
+
+        * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+          did not notice anything (in the latter cases fields below are empty).
+        * fidx - is an index of the function (0 for  target  function, 1 or higher
+          for nonlinear constraints) which is suspected of being "non-C1"
+        * x0[], d[] - arrays of length N which store initial point  and  direction
+          for line search (d[] can be normalized, but does not have to)
+        * stp[], f[] - arrays of length CNT which store step lengths and  function
+          values at these points; f[i] is evaluated in x0+stp[i]*d.
+        * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+          between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+          with  most  likely  position  of  the  violation  between  stpidxa+1 and
+          stpidxa+2.
+
+        You can plot function values stored in stp[]  and  f[]  arrays  and  study
+        behavior of your function by your own eyes, just  to  be  sure  that  test
+        correctly reported C1 violation.
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public class optguardnonc1test0report : apobject
+        {
+            public bool positive;
+            public int fidx;
+            public double[] x0;
+            public double[] d;
+            public int n;
+            public double[] stp;
+            public double[] f;
+            public int cnt;
+            public int stpidxa;
+            public int stpidxb;
+            public optguardnonc1test0report()
+            {
+                init();
+            }
+            public override void init()
+            {
+                x0 = new double[0];
+                d = new double[0];
+                stp = new double[0];
+                f = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                optguardnonc1test0report _result = new optguardnonc1test0report();
+                _result.positive = positive;
+                _result.fidx = fidx;
+                _result.x0 = (double[])x0.Clone();
+                _result.d = (double[])d.Clone();
+                _result.n = n;
+                _result.stp = (double[])stp.Clone();
+                _result.f = (double[])f.Clone();
+                _result.cnt = cnt;
+                _result.stpidxa = stpidxa;
+                _result.stpidxb = stpidxb;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This  structure  is  used  for  detailed   reporting  about  suspected  C1
+        continuity violation as flagged by C1 test #1 (OptGuard  has several tests
+        for C1 continuity, this report is used by #1).
+
+        === WHAT IS TESTED =======================================================
+
+        C1 test #1 studies individual  components  of  the  gradient  as  recorded
+        during line searches. Upon discovering discontinuity in the gradient  this
+        test records specific component which was suspected (or  one  with  highest
+        indication of discontinuity if multiple components are suspected).
+
+        When precise analytic gradient is provided this test is more powerful than
+        test #0  which  works  with  function  values  and  ignores  user-provided
+        gradient.  However,  test  #0  becomes  more   powerful   when   numerical
+        differentiation is employed (in such cases test #1 detects  higher  levels
+        of numerical noise and becomes too conservative).
+
+        This test also tells specific components of the gradient which violate  C1
+        continuity, which makes it more informative than #0, which just tells that
+        continuity is violated.
+
+
+        === WHAT IS REPORTED =====================================================
+
+        Actually, report retrieval function returns TWO report structures:
+
+        * one for most suspicious point found so far (one with highest  change  in
+          the directional derivative), so called "strongest" report
+        * another one for most detailed line search (more function  evaluations  =
+          easier to understand what's going on) which triggered  test #1 criteria,
+          so called "longest" report
+
+        In both cases following fields are returned:
+
+        * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+          did not notice anything (in the latter cases fields below are empty).
+        * fidx - is an index of the function (0 for  target  function, 1 or higher
+          for nonlinear constraints) which is suspected of being "non-C1"
+        * vidx - is an index of the variable in [0,N) with nonsmooth derivative
+        * x0[], d[] - arrays of length N which store initial point  and  direction
+          for line search (d[] can be normalized, but does not have to)
+        * stp[], g[] - arrays of length CNT which store step lengths and  gradient
+          values at these points; g[i] is evaluated in  x0+stp[i]*d  and  contains
+          vidx-th component of the gradient.
+        * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+          between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+          with  most  likely  position  of  the  violation  between  stpidxa+1 and
+          stpidxa+2.
+
+        You can plot function values stored in stp[]  and  g[]  arrays  and  study
+        behavior of your function by your own eyes, just  to  be  sure  that  test
+        correctly reported C1 violation.
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public class optguardnonc1test1report : apobject
+        {
+            public bool positive;
+            public int fidx;
+            public int vidx;
+            public double[] x0;
+            public double[] d;
+            public int n;
+            public double[] stp;
+            public double[] g;
+            public int cnt;
+            public int stpidxa;
+            public int stpidxb;
+            public optguardnonc1test1report()
+            {
+                init();
+            }
+            public override void init()
+            {
+                x0 = new double[0];
+                d = new double[0];
+                stp = new double[0];
+                g = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                optguardnonc1test1report _result = new optguardnonc1test1report();
+                _result.positive = positive;
+                _result.fidx = fidx;
+                _result.vidx = vidx;
+                _result.x0 = (double[])x0.Clone();
+                _result.d = (double[])d.Clone();
+                _result.n = n;
+                _result.stp = (double[])stp.Clone();
+                _result.g = (double[])g.Clone();
+                _result.cnt = cnt;
+                _result.stpidxa = stpidxa;
+                _result.stpidxb = stpidxb;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        This subroutine initializes "internal" OptGuard report,  i.e. one intended
+        for internal use by optimizers.
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void optguardinitinternal(optguardreport rep,
+            int n,
+            int k,
+            alglib.xparams _params)
+        {
+            rep.nonc0suspected = false;
+            rep.nonc0test0positive = false;
+            rep.nonc0lipschitzc = 0;
+            rep.nonc0fidx = -1;
+            rep.nonc1suspected = false;
+            rep.nonc1test0positive = false;
+            rep.nonc1test1positive = false;
+            rep.nonc1lipschitzc = 0;
+            rep.nonc1fidx = -1;
+            rep.badgradsuspected = false;
+            rep.badgradfidx = -1;
+            rep.badgradvidx = -1;
+        }
+
+
+        /*************************************************************************
+        This subroutine exports report to user-readable representation (all arrays
+        are forced to have exactly same size as needed; unused arrays are  set  to
+        zero length).
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void optguardexportreport(optguardreport srcrep,
+            int n,
+            int k,
+            bool badgradhasxj,
+            optguardreport dstrep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            dstrep.nonc0suspected = srcrep.nonc0suspected;
+            dstrep.nonc0test0positive = srcrep.nonc0test0positive;
+            if( srcrep.nonc0suspected )
+            {
+                dstrep.nonc0lipschitzc = srcrep.nonc0lipschitzc;
+                dstrep.nonc0fidx = srcrep.nonc0fidx;
+            }
+            else
+            {
+                dstrep.nonc0lipschitzc = 0;
+                dstrep.nonc0fidx = -1;
+            }
+            dstrep.nonc1suspected = srcrep.nonc1suspected;
+            dstrep.nonc1test0positive = srcrep.nonc1test0positive;
+            dstrep.nonc1test1positive = srcrep.nonc1test1positive;
+            if( srcrep.nonc1suspected )
+            {
+                dstrep.nonc1lipschitzc = srcrep.nonc1lipschitzc;
+                dstrep.nonc1fidx = srcrep.nonc1fidx;
+            }
+            else
+            {
+                dstrep.nonc1lipschitzc = 0;
+                dstrep.nonc1fidx = -1;
+            }
+            dstrep.badgradsuspected = srcrep.badgradsuspected;
+            if( srcrep.badgradsuspected )
+            {
+                dstrep.badgradfidx = srcrep.badgradfidx;
+                dstrep.badgradvidx = srcrep.badgradvidx;
+            }
+            else
+            {
+                dstrep.badgradfidx = -1;
+                dstrep.badgradvidx = -1;
+            }
+            if( badgradhasxj )
+            {
+                dstrep.badgradxbase = new double[n];
+                for(j=0; j<=n-1; j++)
+                {
+                    dstrep.badgradxbase[j] = srcrep.badgradxbase[j];
+                }
+                dstrep.badgraduser = new double[k, n];
+                dstrep.badgradnum = new double[k, n];
+                for(i=0; i<=k-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        dstrep.badgraduser[i,j] = srcrep.badgraduser[i,j];
+                        dstrep.badgradnum[i,j] = srcrep.badgradnum[i,j];
+                    }
+                }
+            }
+            else
+            {
+                dstrep.badgradxbase = new double[0];
+                dstrep.badgraduser = new double[0, 0];
+                dstrep.badgradnum = new double[0, 0];
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine exports report to user-readable representation (all arrays
+        are forced to have exactly same size as needed; unused arrays are  set  to
+        zero length).
+
+        NOTE: we assume that SrcRep contains scaled X0[] and  D[],  i.e.  explicit
+              variable scaling was applied. We need to rescale them during export,
+              that's why we need S[] parameter.
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void smoothnessmonitorexportc1test0report(optguardnonc1test0report srcrep,
+            double[] s,
+            optguardnonc1test0report dstrep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            dstrep.positive = srcrep.positive;
+            if( srcrep.positive )
+            {
+                dstrep.stpidxa = srcrep.stpidxa;
+                dstrep.stpidxb = srcrep.stpidxb;
+                dstrep.fidx = srcrep.fidx;
+                dstrep.cnt = srcrep.cnt;
+                dstrep.n = srcrep.n;
+                dstrep.x0 = new double[srcrep.n];
+                dstrep.d = new double[srcrep.n];
+                for(i=0; i<=srcrep.n-1; i++)
+                {
+                    dstrep.x0[i] = srcrep.x0[i]*s[i];
+                    dstrep.d[i] = srcrep.d[i]*s[i];
+                }
+                dstrep.stp = new double[srcrep.cnt];
+                dstrep.f = new double[srcrep.cnt];
+                for(i=0; i<=srcrep.cnt-1; i++)
+                {
+                    dstrep.stp[i] = srcrep.stp[i];
+                    dstrep.f[i] = srcrep.f[i];
+                }
+            }
+            else
+            {
+                dstrep.stpidxa = -1;
+                dstrep.stpidxb = -1;
+                dstrep.fidx = -1;
+                dstrep.cnt = 0;
+                dstrep.n = 0;
+                dstrep.x0 = new double[0];
+                dstrep.d = new double[0];
+                dstrep.stp = new double[0];
+                dstrep.f = new double[0];
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine exports report to user-readable representation (all arrays
+        are forced to have exactly same size as needed; unused arrays are  set  to
+        zero length).
+
+        NOTE: we assume that SrcRep contains scaled X0[], D[] and G[], i.e. explicit
+              variable scaling was applied. We need to rescale them during export,
+              that's why we need S[] parameter.
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void smoothnessmonitorexportc1test1report(optguardnonc1test1report srcrep,
+            double[] s,
+            optguardnonc1test1report dstrep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            dstrep.positive = srcrep.positive;
+            if( srcrep.positive )
+            {
+                alglib.ap.assert(srcrep.vidx>=0 && srcrep.vidx<srcrep.n, "SmoothnessMonitorExportC1Test1Report: integrity check failed");
+                dstrep.stpidxa = srcrep.stpidxa;
+                dstrep.stpidxb = srcrep.stpidxb;
+                dstrep.fidx = srcrep.fidx;
+                dstrep.vidx = srcrep.vidx;
+                dstrep.cnt = srcrep.cnt;
+                dstrep.n = srcrep.n;
+                dstrep.x0 = new double[srcrep.n];
+                dstrep.d = new double[srcrep.n];
+                for(i=0; i<=srcrep.n-1; i++)
+                {
+                    dstrep.x0[i] = srcrep.x0[i]*s[i];
+                    dstrep.d[i] = srcrep.d[i]*s[i];
+                }
+                dstrep.stp = new double[srcrep.cnt];
+                dstrep.g = new double[srcrep.cnt];
+                for(i=0; i<=srcrep.cnt-1; i++)
+                {
+                    dstrep.stp[i] = srcrep.stp[i];
+                    dstrep.g[i] = srcrep.g[i]/s[srcrep.vidx];
+                }
+            }
+            else
+            {
+                dstrep.stpidxa = -1;
+                dstrep.stpidxb = -1;
+                dstrep.fidx = -1;
+                dstrep.vidx = -1;
+                dstrep.cnt = 0;
+                dstrep.n = 0;
+                dstrep.x0 = new double[0];
+                dstrep.d = new double[0];
+                dstrep.stp = new double[0];
+                dstrep.g = new double[0];
+            }
+        }
+
+
+        /*************************************************************************
+        Returns True when all flags are clear. Intended for easy  coding  of  unit
+        tests.
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool optguardallclear(optguardreport rep,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = !((rep.badgradsuspected || rep.nonc0suspected) || rep.nonc1suspected);
+            return result;
+        }
+
+
+    }
+    public class optserv
+    {
+        /*************************************************************************
+        This structure is used to store temporary buffers for L-BFGS-based preconditioner.
+
+          -- ALGLIB --
+             Copyright 01.07.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public class precbuflbfgs : apobject
+        {
+            public double[] norms;
+            public double[] alpha;
+            public double[] rho;
+            public double[,] yk;
+            public int[] idx;
+            public double[] bufa;
+            public int[] bufb;
+            public precbuflbfgs()
+            {
+                init();
+            }
+            public override void init()
+            {
+                norms = new double[0];
+                alpha = new double[0];
+                rho = new double[0];
+                yk = new double[0,0];
+                idx = new int[0];
+                bufa = new double[0];
+                bufb = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                precbuflbfgs _result = new precbuflbfgs();
+                _result.norms = (double[])norms.Clone();
+                _result.alpha = (double[])alpha.Clone();
+                _result.rho = (double[])rho.Clone();
+                _result.yk = (double[,])yk.Clone();
+                _result.idx = (int[])idx.Clone();
+                _result.bufa = (double[])bufa.Clone();
+                _result.bufb = (int[])bufb.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure is used to store temporary buffers for LowRank preconditioner.
+
+          -- ALGLIB --
+             Copyright 21.08.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public class precbuflowrank : apobject
+        {
+            public int n;
+            public int k;
+            public double[] d;
+            public double[,] v;
+            public double[] bufc;
+            public double[,] bufz;
+            public double[,] bufw;
+            public double[] tmp;
+            public precbuflowrank()
+            {
+                init();
+            }
+            public override void init()
+            {
+                d = new double[0];
+                v = new double[0,0];
+                bufc = new double[0];
+                bufz = new double[0,0];
+                bufw = new double[0,0];
+                tmp = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                precbuflowrank _result = new precbuflowrank();
+                _result.n = n;
+                _result.k = k;
+                _result.d = (double[])d.Clone();
+                _result.v = (double[,])v.Clone();
+                _result.bufc = (double[])bufc.Clone();
+                _result.bufz = (double[,])bufz.Clone();
+                _result.bufw = (double[,])bufw.Clone();
+                _result.tmp = (double[])tmp.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure is a smoothness monitor.
+
+          -- ALGLIB --
+             Copyright 21.08.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public class smoothnessmonitor : apobject
+        {
+            public int n;
+            public int k;
+            public bool checksmoothness;
+            public double[] dcur;
+            public int enqueuedcnt;
+            public double[] enqueuedstp;
+            public double[] enqueuedx;
+            public double[] enqueuedfunc;
+            public double[,] enqueuedjac;
+            public double[] sortedstp;
+            public int[] sortedidx;
+            public int sortedcnt;
+            public bool linesearchspoiled;
+            public bool linesearchstarted;
+            public double nonc0currentrating;
+            public double nonc1currentrating;
+            public bool badgradhasxj;
+            public optguardapi.optguardreport rep;
+            public double nonc1test0strrating;
+            public double nonc1test0lngrating;
+            public optguardapi.optguardnonc1test0report nonc1test0strrep;
+            public optguardapi.optguardnonc1test0report nonc1test0lngrep;
+            public double nonc1test1strrating;
+            public double nonc1test1lngrating;
+            public optguardapi.optguardnonc1test1report nonc1test1strrep;
+            public optguardapi.optguardnonc1test1report nonc1test1lngrep;
+            public bool needfij;
+            public double[] x;
+            public double[] fi;
+            public double[,] j;
+            public rcommstate rstateg0;
+            public double[] xbase;
+            public double[] fbase;
+            public double[] fm;
+            public double[] fc;
+            public double[] fp;
+            public double[] jm;
+            public double[] jc;
+            public double[] jp;
+            public double[,] jbaseusr;
+            public double[,] jbasenum;
+            public double[] stp;
+            public double[] bufr;
+            public double[] f;
+            public double[] g;
+            public double[] deltax;
+            public int[] tmpidx;
+            public int[] bufi;
+            public double[] xu;
+            public double[] du;
+            public double[] f0;
+            public double[,] j0;
+            public smoothnessmonitor()
+            {
+                init();
+            }
+            public override void init()
+            {
+                dcur = new double[0];
+                enqueuedstp = new double[0];
+                enqueuedx = new double[0];
+                enqueuedfunc = new double[0];
+                enqueuedjac = new double[0,0];
+                sortedstp = new double[0];
+                sortedidx = new int[0];
+                rep = new optguardapi.optguardreport();
+                nonc1test0strrep = new optguardapi.optguardnonc1test0report();
+                nonc1test0lngrep = new optguardapi.optguardnonc1test0report();
+                nonc1test1strrep = new optguardapi.optguardnonc1test1report();
+                nonc1test1lngrep = new optguardapi.optguardnonc1test1report();
+                x = new double[0];
+                fi = new double[0];
+                j = new double[0,0];
+                rstateg0 = new rcommstate();
+                xbase = new double[0];
+                fbase = new double[0];
+                fm = new double[0];
+                fc = new double[0];
+                fp = new double[0];
+                jm = new double[0];
+                jc = new double[0];
+                jp = new double[0];
+                jbaseusr = new double[0,0];
+                jbasenum = new double[0,0];
+                stp = new double[0];
+                bufr = new double[0];
+                f = new double[0];
+                g = new double[0];
+                deltax = new double[0];
+                tmpidx = new int[0];
+                bufi = new int[0];
+                xu = new double[0];
+                du = new double[0];
+                f0 = new double[0];
+                j0 = new double[0,0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                smoothnessmonitor _result = new smoothnessmonitor();
+                _result.n = n;
+                _result.k = k;
+                _result.checksmoothness = checksmoothness;
+                _result.dcur = (double[])dcur.Clone();
+                _result.enqueuedcnt = enqueuedcnt;
+                _result.enqueuedstp = (double[])enqueuedstp.Clone();
+                _result.enqueuedx = (double[])enqueuedx.Clone();
+                _result.enqueuedfunc = (double[])enqueuedfunc.Clone();
+                _result.enqueuedjac = (double[,])enqueuedjac.Clone();
+                _result.sortedstp = (double[])sortedstp.Clone();
+                _result.sortedidx = (int[])sortedidx.Clone();
+                _result.sortedcnt = sortedcnt;
+                _result.linesearchspoiled = linesearchspoiled;
+                _result.linesearchstarted = linesearchstarted;
+                _result.nonc0currentrating = nonc0currentrating;
+                _result.nonc1currentrating = nonc1currentrating;
+                _result.badgradhasxj = badgradhasxj;
+                _result.rep = (optguardapi.optguardreport)rep.make_copy();
+                _result.nonc1test0strrating = nonc1test0strrating;
+                _result.nonc1test0lngrating = nonc1test0lngrating;
+                _result.nonc1test0strrep = (optguardapi.optguardnonc1test0report)nonc1test0strrep.make_copy();
+                _result.nonc1test0lngrep = (optguardapi.optguardnonc1test0report)nonc1test0lngrep.make_copy();
+                _result.nonc1test1strrating = nonc1test1strrating;
+                _result.nonc1test1lngrating = nonc1test1lngrating;
+                _result.nonc1test1strrep = (optguardapi.optguardnonc1test1report)nonc1test1strrep.make_copy();
+                _result.nonc1test1lngrep = (optguardapi.optguardnonc1test1report)nonc1test1lngrep.make_copy();
+                _result.needfij = needfij;
+                _result.x = (double[])x.Clone();
+                _result.fi = (double[])fi.Clone();
+                _result.j = (double[,])j.Clone();
+                _result.rstateg0 = (rcommstate)rstateg0.make_copy();
+                _result.xbase = (double[])xbase.Clone();
+                _result.fbase = (double[])fbase.Clone();
+                _result.fm = (double[])fm.Clone();
+                _result.fc = (double[])fc.Clone();
+                _result.fp = (double[])fp.Clone();
+                _result.jm = (double[])jm.Clone();
+                _result.jc = (double[])jc.Clone();
+                _result.jp = (double[])jp.Clone();
+                _result.jbaseusr = (double[,])jbaseusr.Clone();
+                _result.jbasenum = (double[,])jbasenum.Clone();
+                _result.stp = (double[])stp.Clone();
+                _result.bufr = (double[])bufr.Clone();
+                _result.f = (double[])f.Clone();
+                _result.g = (double[])g.Clone();
+                _result.deltax = (double[])deltax.Clone();
+                _result.tmpidx = (int[])tmpidx.Clone();
+                _result.bufi = (int[])bufi.Clone();
+                _result.xu = (double[])xu.Clone();
+                _result.du = (double[])du.Clone();
+                _result.f0 = (double[])f0.Clone();
+                _result.j0 = (double[,])j0.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        public const double ognoiselevelf = 1.0E2*math.machineepsilon;
+        public const double ognoiselevelg = 1.0E4*math.machineepsilon;
+        public const double ogminrating0 = 50.0;
+        public const double ogminrating1 = 50.0;
+
+
+        /*************************************************************************
+        This subroutine checks violation of the box constraints. On output it sets
+        bcerr to the maximum scaled violation, bcidx to the index of the violating
+        constraint.
+
+        if bcerr=0 (say, if no constraints are violated) then bcidx=-1.
+
+        If nonunits=false then s[] is not referenced at all (assumed unit).
+
+          -- ALGLIB --
+             Copyright 7.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void checkbcviolation(bool[] hasbndl,
+            double[] bndl,
+            bool[] hasbndu,
+            double[] bndu,
+            double[] x,
+            int n,
+            double[] s,
+            bool nonunits,
+            ref double bcerr,
+            ref int bcidx,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double vs = 0;
+            double ve = 0;
+
+            bcerr = 0;
+            bcidx = 0;
+
+            bcerr = 0;
+            bcidx = -1;
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Fetch scale
+                //
+                if( nonunits )
+                {
+                    vs = 1/s[i];
+                }
+                else
+                {
+                    vs = 1;
+                }
+                
+                //
+                // Check lower bound
+                //
+                if( hasbndl[i] && x[i]<bndl[i] )
+                {
+                    ve = (bndl[i]-x[i])*vs;
+                    if( ve>bcerr )
+                    {
+                        bcerr = ve;
+                        bcidx = i;
+                    }
+                }
+                
+                //
+                // Check upper bound
+                //
+                if( hasbndu[i] && x[i]>bndu[i] )
+                {
+                    ve = (x[i]-bndu[i])*vs;
+                    if( ve>bcerr )
+                    {
+                        bcerr = ve;
+                        bcidx = i;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine checks violation of the general linear constraints.
+
+        Constraints are assumed to be un-normalized and stored in the format "NEC
+        equality ones followed by NIC inequality ones".
+
+        On output it sets lcerr to the maximum scaled violation, lcidx to the source
+        index of the most violating constraint (row indexes of CLEIC are mapped to
+        the indexes of the "original" constraints via LCSrcIdx[] array.
+
+        if lcerr=0 (say, if no constraints are violated) then lcidx=-1.
+
+        If nonunits=false then s[] is not referenced at all (assumed unit).
+
+          -- ALGLIB --
+             Copyright 7.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void checklcviolation(double[,] cleic,
+            int[] lcsrcidx,
+            int nec,
+            int nic,
+            double[] x,
+            int n,
+            ref double lcerr,
+            ref int lcidx,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double cx = 0;
+            double cnrm = 0;
+            double v = 0;
+
+            lcerr = 0;
+            lcidx = 0;
+
+            lcerr = 0;
+            lcidx = -1;
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                cx = -cleic[i,n];
+                cnrm = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = cleic[i,j];
+                    cx = cx+v*x[j];
+                    cnrm = cnrm+v*v;
+                }
+                cnrm = Math.Sqrt(cnrm);
+                cx = cx/apserv.coalesce(cnrm, 1, _params);
+                if( i<nec )
+                {
+                    cx = Math.Abs(cx);
+                }
+                else
+                {
+                    cx = Math.Max(cx, 0);
+                }
+                if( cx>lcerr )
+                {
+                    lcerr = cx;
+                    lcidx = lcsrcidx[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine checks violation of the nonlinear constraints. Fi[0] is the
+        target value (ignored), Fi[1:NG+NH] are values of nonlinear constraints.
+
+        On output it sets nlcerr to the scaled violation, nlcidx to the index
+        of the most violating constraint.
+
+        if nlcerr=0 (say, if no constraints are violated) then nlcidx=-1.
+
+        If nonunits=false then s[] is not referenced at all (assumed unit).
+
+          -- ALGLIB --
+             Copyright 7.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void checknlcviolation(double[] fi,
+            int ng,
+            int nh,
+            ref double nlcerr,
+            ref int nlcidx,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double v = 0;
+
+            nlcerr = 0;
+            nlcidx = 0;
+
+            nlcerr = 0;
+            nlcidx = -1;
+            for(i=0; i<=ng+nh-1; i++)
+            {
+                v = fi[i+1];
+                if( i<ng )
+                {
+                    v = Math.Abs(v);
+                }
+                else
+                {
+                    v = Math.Max(v, 0);
+                }
+                if( v>nlcerr )
+                {
+                    nlcerr = v;
+                    nlcidx = i;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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(double[] x,
+            double[] bl,
+            bool[] havebl,
+            double[] bu,
+            bool[] havebu,
+            int nmain,
+            int nslack,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                    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, _params);
+                    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, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+            bool wasactivated = new bool();
+
+            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++)
+            {
+                wasactivated = (double)(x[i])!=(double)(xprev[i]) && ((havebndl[i] && (double)(x[i])==(double)(bndl[i])) || (havebndu[i] && (double)(x[i])==(double)(bndu[i])));
+                wasactivated = wasactivated || variabletofreeze==i;
+                if( wasactivated )
+                {
+                    result = result+1;
+                }
+            }
+            for(i=0; i<=nslack-1; i++)
+            {
+                wasactivated = (double)(x[nmain+i])!=(double)(xprev[nmain+i]) && (double)(x[nmain+i])==(double)(0.0);
+                wasactivated = wasactivated || variabletofreeze==nmain+i;
+                if( wasactivated )
+                {
+                    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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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[] tau = new double[0];
+            double[] s = new double[0];
+            double armijostep = 0;
+            double armijobeststep = 0;
+            double armijobestfeas = 0;
+            double v = 0;
+            double vv = 0;
+            double mx = 0;
+            double feaserr = 0;
+            double feaserr0 = 0;
+            double feaserr1 = 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[] p1 = new int[0];
+            int[] p2 = new int[0];
+            apserv.apbuffers buf = new apserv.apbuffers();
+            int itscount = 0;
+            int itswithintolerance = 0;
+            int maxitswithintolerance = 0;
+            int badits = 0;
+            int maxbadits = 0;
+            int gparuns = 0;
+            int maxarmijoruns = 0;
+            double[,] permce = new double[0,0];
+            double[,] q = new double[0,0];
+            int i_ = 0;
+
+            ce = (double[,])ce.Clone();
+            qpits = 0;
+            gpaits = 0;
+
+            maxitswithintolerance = 3;
+            maxbadits = 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(x, bndl, havebndl, bndu, havebndu, nmain, nslack, _params) )
+            {
+                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];
+            permce = 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;
+            badits = 0;
+            itscount = 0;
+            while( true )
+            {
+                
+                //
+                // Stage 0: check for exact convergence
+                //
+                converged = true;
+                feaserr = feasibilityerror(ce, x, nmain, nslack, k, ref tmpk, _params);
+                for(i=0; i<=k-1; i++)
+                {
+                    
+                    //
+                    // Calculate 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.
+                    //
+                    // NOTE:      it is important to calculate feasibility error with dedicated
+                    //            function. Once we had a situation when usage of "inline" code
+                    //            resulted in different numerical values calculated at different
+                    //            parts of program for exactly same X. However, this value is
+                    //            essential for algorithm's ability to terminate before entering
+                    //            infinite loop, so reproducibility of numerical results is very
+                    //            important.
+                    //
+                    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];
+                    }
+                    converged = converged && (double)(Math.Abs(v))<=(double)(100*math.machineepsilon*mx);
+                }
+                feaserr0 = 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;
+                    feasibilityerrorgrad(ce, x, nmain, nslack, k, ref armijobestfeas, g, ref tmpk, _params);
+                    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, _params);
+                    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(xa, bndl, havebndl, bndu, havebndu, nmain, nslack, _params);
+                        feaserr = feasibilityerror(ce, xa, nmain, nslack, k, ref tmpk, _params);
+                        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(x, bndl, havebndl, bndu, havebndu, nmain, nslack, _params);
+                    
+                    //
+                    // 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: CE is reordered to PermCE, X is reordered to PermX
+                    //
+                    tsort.tagsortbuf(ref activeconstraints, nmain+nslack, ref p1, ref p2, buf, _params);
+                    for(i=0; i<=k-1; i++)
+                    {
+                        for(j=0; j<=nmain+nslack-1; j++)
+                        {
+                            permce[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 = permce[i,idx0];
+                                permce[i,idx0] = permce[i,idx1];
+                                permce[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 += permce[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*permce[i,i_];
+                        }
+                    }
+                    
+                    //
+                    // Calculate Newton step using pseudoinverse PermCE:
+                    //     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                
+                    //     H^(-1) can be calculated via QR or LQ decomposition (see below)
+                    //     step   = -H^(-1)*g
+                    //
+                    // NOTE: PermCE is destroyed after this block
+                    //
+                    for(i=0; i<=nmain+nslack-1; i++)
+                    {
+                        newtonstep[i] = 0;
+                    }
+                    if( k<=nfree )
+                    {
+                        
+                        //
+                        // CEi    = L*Q
+                        // H      = Q'*L'*L*Q
+                        // inv(H) = Q'*inv(L)*inv(L')*Q
+                        //
+                        // NOTE: we apply minor regularizing perturbation to diagonal of L,
+                        //       which is equal to 10*K*Eps
+                        //
+                        ortfac.rmatrixlq(ref permce, k, nfree, ref tau, _params);
+                        ortfac.rmatrixlqunpackq(permce, k, nfree, tau, k, ref q, _params);
+                        v = 0;
+                        for(i=0; i<=k-1; i++)
+                        {
+                            v = Math.Max(v, Math.Abs(permce[i,i]));
+                        }
+                        v = apserv.coalesce(v, 1, _params);
+                        for(i=0; i<=k-1; i++)
+                        {
+                            permce[i,i] = permce[i,i]+10*k*math.machineepsilon*v;
+                        }
+                        ablas.rmatrixgemv(k, nfree, 1.0, q, 0, 0, 0, g, 0, 0.0, tmpk, 0, _params);
+                        ablas.rmatrixtrsv(k, permce, 0, 0, false, false, 1, tmpk, 0, _params);
+                        ablas.rmatrixtrsv(k, permce, 0, 0, false, false, 0, tmpk, 0, _params);
+                        ablas.rmatrixgemv(nfree, k, -1.0, q, 0, 0, 1, tmpk, 0, 0.0, newtonstep, 0, _params);
+                    }
+                    else
+                    {
+                        
+                        //
+                        // CEi    = Q*R
+                        // H      = R'*R
+                        // inv(H) = inv(R)*inv(R')
+                        //
+                        // NOTE: we apply minor regularizing perturbation to diagonal of R,
+                        //       which is equal to 10*K*Eps
+                        //
+                        ortfac.rmatrixqr(ref permce, k, nfree, ref tau, _params);
+                        v = 0;
+                        for(i=0; i<=nfree-1; i++)
+                        {
+                            v = Math.Max(v, Math.Abs(permce[i,i]));
+                        }
+                        v = apserv.coalesce(v, 1, _params);
+                        for(i=0; i<=nfree-1; i++)
+                        {
+                            vv = 10*nfree*math.machineepsilon*v;
+                            if( (double)(permce[i,i])<(double)(0) )
+                            {
+                                vv = -vv;
+                            }
+                            permce[i,i] = permce[i,i]+vv;
+                        }
+                        for(i_=0; i_<=nfree-1;i_++)
+                        {
+                            newtonstep[i_] = -g[i_];
+                        }
+                        ablas.rmatrixtrsv(nfree, permce, 0, 0, true, false, 1, newtonstep, 0, _params);
+                        ablas.rmatrixtrsv(nfree, permce, 0, 0, true, false, 0, newtonstep, 0, _params);
+                    }
+                    
+                    //
+                    // Post-reordering of Newton step
+                    //
+                    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, _params);
+                    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, _params);
+                    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 = feasibilityerror(ce, xn, nmain, nslack, k, ref tmpk, _params);
+                        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(xa, bndl, havebndl, bndu, havebndu, nmain, nslack, _params);
+                            feaserr = feasibilityerror(ce, xa, nmain, nslack, k, ref tmpk, _params);
+                            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(xa, bndl, havebndl, bndu, havebndu, nmain, nslack, _params);
+                    }
+                    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 = feasibilityerror(ce, x, nmain, nslack, k, ref tmpk, _params);
+                    feasnew = feasibilityerror(ce, xa, nmain, nslack, k, ref tmpk, _params);
+                    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
+                    //
+                    feasibilityerrorgrad(ce, x, nmain, nslack, k, ref feaserr, g, ref tmpk, _params);
+                    
+                    //
+                    // 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, _params);
+                    filterdirection(ref pg, x, bndl, havebndl, bndu, havebndu, s, nmain, nslack, 1.0E-9, _params);
+                    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_];
+                    }
+                    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;
+                    ablas.rmatrixgemv(k, nmain+nslack, 1.0, ce, 0, 0, 0, pg, 0, 0.0, tmpk, 0, _params);
+                    for(i=0; i<=k-1; i++)
+                    {
+                        vd = vd+math.sqr(tmpk[i]);
+                    }
+                    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, _params);
+                    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, _params);
+                    
+                    //
+                    // update X
+                    // check stopping criteria
+                    //
+                    werechangesinconstraints = werechangesinconstraints || numberofchangedconstraints(xn, x, bndl, havebndl, bndu, havebndu, nmain, nslack, _params)>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 = feasibilityerror(ce, x, nmain, nslack, k, ref tmpk, _params);
+                feaserr1 = feaserr;
+                if( (double)(feaserr1)>=(double)(feaserr0*(1-1000*math.machineepsilon)) )
+                {
+                    apserv.inc(ref badits, _params);
+                }
+                else
+                {
+                    badits = 0;
+                }
+                if( (double)(feaserr)<=(double)(epsi) )
+                {
+                    apserv.inc(ref itswithintolerance, _params);
+                }
+                else
+                {
+                    itswithintolerance = 0;
+                }
+                if( (!werechangesinconstraints || itswithintolerance>=maxitswithintolerance) || badits>=maxbadits )
+                {
+                    result = (double)(feaserr)<=(double)(epsi);
+                    return result;
+                }
+                itscount = itscount+1;
+                
+                //
+                // Block below is never executed; it is necessary just to avoid
+                // "unreachable code" warning about automatically generated code.
+                //
+                // We just need a way to transfer control to the end of the function,
+                // even a fake way which is never actually traversed.
+                //
+                if( apserv.alwaysfalse(_params) )
+                {
+                    result = false;
+                    alglib.ap.assert(false);
+                    break;
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+            This function checks that input derivatives are right. First it scales
+        parameters DF0 and DF1 from segment [A;B] to [0;1]. Then it builds Hermite
+        spline and derivative of it in 0.5. Search scale as Max(DF0,DF1, |F0-F1|).
+        Right derivative has to satisfy condition:
+            |H-F|/S<=0,001, |H'-F'|/S<=0,001.
+            
+        INPUT PARAMETERS:
+            F0  -   function's value in X-TestStep point;
+            DF0 -   derivative's value in X-TestStep point;
+            F1  -   function's value in X+TestStep point;
+            DF1 -   derivative's value in X+TestStep point;
+            F   -   testing function's value;
+            DF  -   testing derivative's value;
+           Width-   width of verification segment.
+
+        RESULT:
+            If input derivatives is right then function returns true, else 
+            function returns false.
+            
+          -- ALGLIB --
+             Copyright 29.05.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool derivativecheck(double f0,
+            double df0,
+            double f1,
+            double df1,
+            double f,
+            double df,
+            double width,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            double s = 0;
+            double h = 0;
+            double dh = 0;
+
+            
+            //
+            // Rescale input data to [0,1]
+            //
+            df = width*df;
+            df0 = width*df0;
+            df1 = width*df1;
+            
+            //
+            // Compute error scale, two sources are used:
+            // * magnitudes of derivatives and secants
+            // * magnitudes of input data times sqrt(machine_epsilon)
+            //
+            s = 0.0;
+            s = Math.Max(s, Math.Abs(df0));
+            s = Math.Max(s, Math.Abs(df1));
+            s = Math.Max(s, Math.Abs(f1-f0));
+            s = Math.Max(s, Math.Sqrt(math.machineepsilon)*Math.Abs(f0));
+            s = Math.Max(s, Math.Sqrt(math.machineepsilon)*Math.Abs(f1));
+            
+            //
+            // Compute H and dH/dX at the middle of interval
+            //
+            h = 0.5*(f0+f1)+0.125*(df0-df1);
+            dh = 1.5*(f1-f0)-0.250*(df0+df1);
+            
+            //
+            // Check
+            //
+            if( (double)(s)!=(double)(0) )
+            {
+                if( (double)(Math.Abs(h-f)/s)>(double)(0.001) || (double)(Math.Abs(dh-df)/s)>(double)(0.001) )
+                {
+                    result = false;
+                    return result;
+                }
+            }
+            else
+            {
+                if( (double)(h-f)!=(double)(0.0) || (double)(dh-df)!=(double)(0.0) )
+                {
+                    result = false;
+                    return result;
+                }
+            }
+            result = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Having quadratic target function
+
+            f(x) = 0.5*x'*A*x + b'*x + penaltyfactor*0.5*(C*x-b)'*(C*x-b)
+            
+        and its parabolic model along direction D
+
+            F(x0+alpha*D) = D2*alpha^2 + D1*alpha
+            
+        this function estimates numerical errors in the coefficients of the model.
+            
+        It is important that this  function  does  NOT calculate D1/D2  -  it only
+        estimates numerical errors introduced during evaluation and compares their
+        magnitudes against magnitudes of numerical errors. As result, one of three
+        outcomes is returned for each coefficient:
+            * "true" coefficient is almost surely positive
+            * "true" coefficient is almost surely negative
+            * numerical errors in coefficient are so large that it can not be
+              reliably distinguished from zero
+
+        INPUT PARAMETERS:
+            AbsASum -   SUM(|A[i,j]|)
+            AbsASum2-   SUM(A[i,j]^2)
+            MB      -   max(|B|)
+            MX      -   max(|X|)
+            MD      -   max(|D|)
+            D1      -   linear coefficient
+            D2      -   quadratic coefficient
+
+        OUTPUT PARAMETERS:
+            D1Est   -   estimate of D1 sign,  accounting  for  possible  numerical
+                        errors:
+                        * >0    means "almost surely positive" (D1>0 and large)
+                        * <0    means "almost surely negative" (D1<0 and large)
+                        * =0    means "pessimistic estimate  of  numerical  errors
+                                in D1 is larger than magnitude of D1 itself; it is
+                                impossible to reliably distinguish D1 from zero".
+            D2Est   -   estimate of D2 sign,  accounting  for  possible  numerical
+                        errors:
+                        * >0    means "almost surely positive" (D2>0 and large)
+                        * <0    means "almost surely negative" (D2<0 and large)
+                        * =0    means "pessimistic estimate  of  numerical  errors
+                                in D2 is larger than magnitude of D2 itself; it is
+                                impossible to reliably distinguish D2 from zero".
+                    
+          -- ALGLIB --
+             Copyright 14.05.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void estimateparabolicmodel(double absasum,
+            double absasum2,
+            double mx,
+            double mb,
+            double md,
+            double d1,
+            double d2,
+            ref int d1est,
+            ref int d2est,
+            alglib.xparams _params)
+        {
+            double d1esterror = 0;
+            double d2esterror = 0;
+            double eps = 0;
+            double e1 = 0;
+            double e2 = 0;
+
+            d1est = 0;
+            d2est = 0;
+
+            
+            //
+            // Error estimates:
+            //
+            // * error in D1=d'*(A*x+b) is estimated as
+            //   ED1 = eps*MAX_ABS(D)*(MAX_ABS(X)*ENORM(A)+MAX_ABS(B))
+            // * error in D2=0.5*d'*A*d is estimated as
+            //   ED2 = eps*MAX_ABS(D)^2*ENORM(A)
+            //
+            // Here ENORM(A) is some pseudo-norm which reflects the way numerical
+            // error accumulates during addition. Two ways of accumulation are
+            // possible - worst case (errors always increase) and mean-case (errors
+            // may cancel each other). We calculate geometrical average of both:
+            // * ENORM_WORST(A) = SUM(|A[i,j]|)         error in N-term sum grows as O(N)
+            // * ENORM_MEAN(A)  = SQRT(SUM(A[i,j]^2))   error in N-term sum grows as O(sqrt(N))
+            // * ENORM(A)       = SQRT(ENORM_WORST(A),ENORM_MEAN(A))
+            //
+            eps = 4*math.machineepsilon;
+            e1 = eps*md*(mx*absasum+mb);
+            e2 = eps*md*(mx*Math.Sqrt(absasum2)+mb);
+            d1esterror = Math.Sqrt(e1*e2);
+            if( (double)(Math.Abs(d1))<=(double)(d1esterror) )
+            {
+                d1est = 0;
+            }
+            else
+            {
+                d1est = Math.Sign(d1);
+            }
+            e1 = eps*md*md*absasum;
+            e2 = eps*md*md*Math.Sqrt(absasum2);
+            d2esterror = Math.Sqrt(e1*e2);
+            if( (double)(Math.Abs(d2))<=(double)(d2esterror) )
+            {
+                d2est = 0;
+            }
+            else
+            {
+                d2est = Math.Sign(d2);
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates inexact rank-K preconditioner for Hessian  matrix
+        H=D+W'*C*W, where:
+        * H is a Hessian matrix, which is approximated by D/W/C
+        * D is a diagonal matrix with positive entries
+        * W is a rank-K correction
+        * C is a diagonal factor of rank-K correction
+
+        This preconditioner is inexact but fast - it requires O(N*K)  time  to  be
+        applied. Its main purpose - to be  used  in  barrier/penalty/AUL  methods,
+        where ill-conditioning is created by combination of two factors:
+        * simple bounds on variables => ill-conditioned D
+        * general barrier/penalty => correction W  with large coefficient C (makes
+          problem ill-conditioned) but W itself is well conditioned.
+
+        Preconditioner P is calculated by artificially constructing a set of  BFGS
+        updates which tries to reproduce behavior of H:
+        * Sk = Wk (k-th row of W)
+        * Yk = (D+Wk'*Ck*Wk)*Sk
+        * Yk/Sk are reordered by ascending of C[k]*norm(Wk)^2
+
+        Here we assume that rows of Wk are orthogonal or nearly orthogonal,  which
+        allows us to have O(N*K+K^2) update instead of O(N*K^2) one. Reordering of
+        updates is essential for having good performance on non-orthogonal problems
+        (updates which do not add much of curvature are added first,  and  updates
+        which add very large eigenvalues are added last and override effect of the
+        first updates).
+
+        On input this function takes direction S and components of H.
+        On output it returns inv(H)*S
+
+          -- ALGLIB --
+             Copyright 30.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void inexactlbfgspreconditioner(double[] s,
+            int n,
+            double[] d,
+            double[] c,
+            double[,] w,
+            int k,
+            precbuflbfgs buf,
+            alglib.xparams _params)
+        {
+            int idx = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double vx = 0;
+            double vy = 0;
+            int i_ = 0;
+
+            apserv.rvectorsetlengthatleast(ref buf.norms, k, _params);
+            apserv.rvectorsetlengthatleast(ref buf.alpha, k, _params);
+            apserv.rvectorsetlengthatleast(ref buf.rho, k, _params);
+            apserv.rmatrixsetlengthatleast(ref buf.yk, k, n, _params);
+            apserv.ivectorsetlengthatleast(ref buf.idx, k, _params);
+            
+            //
+            // Check inputs
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert((double)(d[i])>(double)(0), "InexactLBFGSPreconditioner: D[]<=0");
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                alglib.ap.assert((double)(c[i])>=(double)(0), "InexactLBFGSPreconditioner: C[]<0");
+            }
+            
+            //
+            // Reorder linear terms according to increase of second derivative.
+            // Fill Norms[] array.
+            //
+            for(idx=0; idx<=k-1; idx++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += w[idx,i_]*w[idx,i_];
+                }
+                buf.norms[idx] = v*c[idx];
+                buf.idx[idx] = idx;
+            }
+            tsort.tagsortfasti(ref buf.norms, ref buf.idx, ref buf.bufa, ref buf.bufb, k, _params);
+            
+            //
+            // Apply updates
+            //
+            for(idx=0; idx<=k-1; idx++)
+            {
+                
+                //
+                // Select update to perform (ordered by ascending of second derivative)
+                //
+                i = buf.idx[idx];
+                
+                //
+                // Calculate YK and Rho
+                //
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += w[i,i_]*w[i,i_];
+                }
+                v = v*c[i];
+                for(j=0; j<=n-1; j++)
+                {
+                    buf.yk[i,j] = (d[j]+v)*w[i,j];
+                }
+                v = 0.0;
+                v0 = 0.0;
+                v1 = 0.0;
+                for(j=0; j<=n-1; j++)
+                {
+                    vx = w[i,j];
+                    vy = buf.yk[i,j];
+                    v = v+vx*vy;
+                    v0 = v0+vx*vx;
+                    v1 = v1+vy*vy;
+                }
+                if( ((double)(v)>(double)(0) && (double)(v0*v1)>(double)(0)) && (double)(v/Math.Sqrt(v0*v1))>(double)(n*10*math.machineepsilon) )
+                {
+                    buf.rho[i] = 1/v;
+                }
+                else
+                {
+                    buf.rho[i] = 0.0;
+                }
+            }
+            for(idx=k-1; idx>=0; idx--)
+            {
+                
+                //
+                // Select update to perform (ordered by ascending of second derivative)
+                //
+                i = buf.idx[idx];
+                
+                //
+                // Calculate Alpha[] according to L-BFGS algorithm
+                // and update S[]
+                //
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += w[i,i_]*s[i_];
+                }
+                v = buf.rho[i]*v;
+                buf.alpha[i] = v;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    s[i_] = s[i_] - v*buf.yk[i,i_];
+                }
+            }
+            for(j=0; j<=n-1; j++)
+            {
+                s[j] = s[j]/d[j];
+            }
+            for(idx=0; idx<=k-1; idx++)
+            {
+                
+                //
+                // Select update to perform (ordered by ascending of second derivative)
+                //
+                i = buf.idx[idx];
+                
+                //
+                // Calculate Beta according to L-BFGS algorithm
+                // and update S[]
+                //
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += buf.yk[i,i_]*s[i_];
+                }
+                v = buf.alpha[i]-buf.rho[i]*v;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    s[i_] = s[i_] + v*w[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function prepares exact low-rank preconditioner  for  Hessian  matrix
+        H=D+W'*C*W, where:
+        * H is a Hessian matrix, which is approximated by D/W/C
+        * D is a diagonal matrix with positive entries
+        * W is a rank-K correction
+        * C is a diagonal factor of rank-K correction, positive semidefinite
+
+        This preconditioner is exact but relatively slow -  it  requires  O(N*K^2)
+        time to be prepared and O(N*K) time to be applied. It is  calculated  with
+        the help of Woodbury matrix identity.
+
+        It should be used as follows:
+        * PrepareLowRankPreconditioner() call PREPARES data structure
+        * subsequent calls to ApplyLowRankPreconditioner() APPLY preconditioner to
+          user-specified search direction.
+
+          -- ALGLIB --
+             Copyright 30.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void preparelowrankpreconditioner(double[] d,
+            double[] c,
+            double[,] w,
+            int n,
+            int k,
+            precbuflowrank buf,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            bool b = new bool();
+
+            
+            //
+            // Check inputs
+            //
+            alglib.ap.assert(n>0, "PrepareLowRankPreconditioner: N<=0");
+            alglib.ap.assert(k>=0, "PrepareLowRankPreconditioner: N<=0");
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert((double)(d[i])>(double)(0), "PrepareLowRankPreconditioner: D[]<=0");
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                alglib.ap.assert((double)(c[i])>=(double)(0), "PrepareLowRankPreconditioner: C[]<0");
+            }
+            
+            //
+            // Prepare buffer structure; skip zero entries of update.
+            //
+            apserv.rvectorsetlengthatleast(ref buf.d, n, _params);
+            apserv.rmatrixsetlengthatleast(ref buf.v, k, n, _params);
+            apserv.rvectorsetlengthatleast(ref buf.bufc, k, _params);
+            apserv.rmatrixsetlengthatleast(ref buf.bufw, k+1, n, _params);
+            buf.n = n;
+            buf.k = 0;
+            for(i=0; i<=k-1; i++)
+            {
+                
+                //
+                // Estimate magnitude of update row; skip zero rows (either W or C are zero)
+                //
+                v = 0.0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+w[i,j]*w[i,j];
+                }
+                v = v*c[i];
+                if( (double)(v)==(double)(0) )
+                {
+                    continue;
+                }
+                alglib.ap.assert((double)(v)>(double)(0), "PrepareLowRankPreconditioner: internal error");
+                
+                //
+                // Copy non-zero update to buffer
+                //
+                buf.bufc[buf.k] = c[i];
+                for(j=0; j<=n-1; j++)
+                {
+                    buf.v[buf.k,j] = w[i,j];
+                    buf.bufw[buf.k,j] = w[i,j];
+                }
+                apserv.inc(ref buf.k, _params);
+            }
+            
+            //
+            // Reset K (for convenience)
+            //
+            k = buf.k;
+            
+            //
+            // Prepare diagonal factor; quick exit for K=0
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                buf.d[i] = 1/d[i];
+            }
+            if( k==0 )
+            {
+                return;
+            }
+            
+            //
+            // Use Woodbury matrix identity
+            //
+            apserv.rmatrixsetlengthatleast(ref buf.bufz, k, k, _params);
+            for(i=0; i<=k-1; i++)
+            {
+                for(j=0; j<=k-1; j++)
+                {
+                    buf.bufz[i,j] = 0.0;
+                }
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                buf.bufz[i,i] = 1/buf.bufc[i];
+            }
+            for(j=0; j<=n-1; j++)
+            {
+                buf.bufw[k,j] = 1/Math.Sqrt(d[j]);
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    buf.bufw[i,j] = buf.bufw[i,j]*buf.bufw[k,j];
+                }
+            }
+            ablas.rmatrixgemm(k, k, n, 1.0, buf.bufw, 0, 0, 0, buf.bufw, 0, 0, 1, 1.0, buf.bufz, 0, 0, _params);
+            b = trfac.spdmatrixcholeskyrec(ref buf.bufz, 0, k, true, ref buf.tmp, _params);
+            alglib.ap.assert(b, "PrepareLowRankPreconditioner: internal error (Cholesky failure)");
+            ablas.rmatrixlefttrsm(k, n, buf.bufz, 0, 0, true, false, 1, buf.v, 0, 0, _params);
+            for(i=0; i<=k-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    buf.v[i,j] = buf.v[i,j]*buf.d[j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function apply exact low-rank preconditioner prepared by
+        PrepareLowRankPreconditioner function (see its comments for more information).
+
+          -- ALGLIB --
+             Copyright 30.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void applylowrankpreconditioner(double[] s,
+            precbuflowrank buf,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int k = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            n = buf.n;
+            k = buf.k;
+            apserv.rvectorsetlengthatleast(ref buf.tmp, n, _params);
+            for(j=0; j<=n-1; j++)
+            {
+                buf.tmp[j] = buf.d[j]*s[j];
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                v = 0.0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+buf.v[i,j]*s[j];
+                }
+                for(j=0; j<=n-1; j++)
+                {
+                    buf.tmp[j] = buf.tmp[j]-v*buf.v[i,j];
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                s[i] = buf.tmp[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine initializes smoothness monitor at  the  beginning  of  the
+        optimization session.
+
+        It is possible to perform "dummy" initialization with N=K=0.
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void smoothnessmonitorinit(smoothnessmonitor monitor,
+            int n,
+            int k,
+            bool checksmoothness,
+            alglib.xparams _params)
+        {
+            monitor.n = n;
+            monitor.k = k;
+            monitor.checksmoothness = checksmoothness;
+            monitor.linesearchspoiled = false;
+            monitor.linesearchstarted = false;
+            monitor.enqueuedcnt = 0;
+            monitor.sortedcnt = 0;
+            monitor.nonc0currentrating = 0.0;
+            monitor.nonc1currentrating = 0.0;
+            optguardapi.optguardinitinternal(monitor.rep, n, k, _params);
+            monitor.nonc1test0strrating = 0.0;
+            monitor.nonc1test0lngrating = 0.0;
+            monitor.nonc1test0strrep.positive = false;
+            monitor.nonc1test0lngrep.positive = false;
+            monitor.nonc1test1strrating = 0.0;
+            monitor.nonc1test1lngrating = 0.0;
+            monitor.nonc1test1strrep.positive = false;
+            monitor.nonc1test1lngrep.positive = false;
+            monitor.badgradhasxj = false;
+            monitor.rstateg0.ia = new int[4+1];
+            monitor.rstateg0.ra = new double[3+1];
+            monitor.rstateg0.stage = -1;
+        }
+
+
+        /*************************************************************************
+        This subroutine starts line search
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void smoothnessmonitorstartlinesearch(smoothnessmonitor monitor,
+            double[] x,
+            double[] fi,
+            double[,] jac,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int k = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            n = monitor.n;
+            k = monitor.k;
+            
+            //
+            // Skip if inactive or spoiled by NAN
+            //
+            if( !monitor.checksmoothness )
+            {
+                return;
+            }
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.5*v+x[i];
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                v = 0.5*v+fi[i];
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    v = 0.5*v+jac[i,j];
+                }
+            }
+            if( !math.isfinite(v) )
+            {
+                monitor.linesearchspoiled = true;
+                return;
+            }
+            
+            //
+            // Finalize previous line search
+            //
+            if( monitor.enqueuedcnt>0 )
+            {
+                smoothnessmonitorfinalizelinesearch(monitor, _params);
+            }
+            
+            //
+            // Store initial point
+            //
+            monitor.linesearchstarted = true;
+            monitor.enqueuedcnt = 1;
+            apserv.rvectorgrowto(ref monitor.enqueuedstp, monitor.enqueuedcnt, _params);
+            apserv.rvectorgrowto(ref monitor.enqueuedx, monitor.enqueuedcnt*n, _params);
+            apserv.rvectorgrowto(ref monitor.enqueuedfunc, monitor.enqueuedcnt*k, _params);
+            apserv.rmatrixgrowrowsto(ref monitor.enqueuedjac, monitor.enqueuedcnt*k, n, _params);
+            monitor.enqueuedstp[0] = 0.0;
+            for(j=0; j<=n-1; j++)
+            {
+                monitor.enqueuedx[j] = x[j];
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                monitor.enqueuedfunc[i] = fi[i];
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    monitor.enqueuedjac[i,j] = jac[i,j];
+                }
+            }
+            
+            //
+            // Initialize sorted representation
+            //
+            apserv.rvectorgrowto(ref monitor.sortedstp, 1, _params);
+            apserv.ivectorgrowto(ref monitor.sortedidx, 1, _params);
+            monitor.sortedstp[0] = 0.0;
+            monitor.sortedidx[0] = 0;
+            monitor.sortedcnt = 1;
+        }
+
+
+        /*************************************************************************
+        This subroutine starts line search for a scalar function - convenience
+        wrapper for ....StartLineSearch() with unscaled variables.
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void smoothnessmonitorstartlinesearch1u(smoothnessmonitor monitor,
+            double[] s,
+            double[] invs,
+            double[] x,
+            double f0,
+            double[] j0,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int k = 0;
+            int i = 0;
+
+            n = monitor.n;
+            k = monitor.k;
+            if( !monitor.checksmoothness )
+            {
+                return;
+            }
+            alglib.ap.assert(k==1, "SmoothnessMonitorStartLineSearch1: K<>1");
+            apserv.rvectorsetlengthatleast(ref monitor.xu, n, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.f0, 1, _params);
+            apserv.rmatrixsetlengthatleast(ref monitor.j0, 1, n, _params);
+            monitor.f0[0] = f0;
+            for(i=0; i<=n-1; i++)
+            {
+                monitor.xu[i] = x[i]*invs[i];
+                monitor.j0[0,i] = j0[i]*s[i];
+            }
+            smoothnessmonitorstartlinesearch(monitor, monitor.xu, monitor.f0, monitor.j0, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine enqueues one more trial point
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void smoothnessmonitorenqueuepoint(smoothnessmonitor monitor,
+            double[] d,
+            double stp,
+            double[] x,
+            double[] fi,
+            double[,] jac,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int k = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int enqueuedcnt = 0;
+            int sortedcnt = 0;
+            bool hasduplicates = new bool();
+            int funcidx = 0;
+            int stpidx = 0;
+            double f0 = 0;
+            double f1 = 0;
+            double f2 = 0;
+            double f3 = 0;
+            double f4 = 0;
+            double noise0 = 0;
+            double noise1 = 0;
+            double noise2 = 0;
+            double noise3 = 0;
+            double rating = 0;
+            double lipschitz = 0;
+
+            n = monitor.n;
+            k = monitor.k;
+            
+            //
+            // Skip if inactive or spoiled by NAN
+            //
+            if( (!monitor.checksmoothness || monitor.linesearchspoiled) || !monitor.linesearchstarted )
+            {
+                return;
+            }
+            v = stp;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.5*v+x[i];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.5*v+d[i];
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                v = 0.5*v+fi[i];
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    v = 0.5*v+jac[i,j];
+                }
+            }
+            if( !math.isfinite(v) )
+            {
+                monitor.linesearchspoiled = true;
+                return;
+            }
+            
+            //
+            // Enqueue
+            //
+            apserv.inc(ref monitor.enqueuedcnt, _params);
+            enqueuedcnt = monitor.enqueuedcnt;
+            apserv.rvectorgrowto(ref monitor.dcur, n, _params);
+            apserv.rvectorgrowto(ref monitor.enqueuedstp, enqueuedcnt, _params);
+            apserv.rvectorgrowto(ref monitor.enqueuedx, enqueuedcnt*n, _params);
+            apserv.rvectorgrowto(ref monitor.enqueuedfunc, enqueuedcnt*k, _params);
+            apserv.rmatrixgrowrowsto(ref monitor.enqueuedjac, enqueuedcnt*k, n, _params);
+            monitor.enqueuedstp[enqueuedcnt-1] = stp;
+            for(j=0; j<=n-1; j++)
+            {
+                monitor.dcur[j] = d[j];
+            }
+            for(j=0; j<=n-1; j++)
+            {
+                monitor.enqueuedx[(enqueuedcnt-1)*n+j] = x[j];
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                monitor.enqueuedfunc[(enqueuedcnt-1)*k+i] = fi[i];
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    monitor.enqueuedjac[(enqueuedcnt-1)*k+i,j] = jac[i,j];
+                }
+            }
+            
+            //
+            // Update sorted representation: insert to the end, reorder
+            //
+            sortedcnt = monitor.sortedcnt;
+            hasduplicates = false;
+            for(i=0; i<=sortedcnt-1; i++)
+            {
+                hasduplicates = hasduplicates || monitor.sortedstp[i]==stp;
+            }
+            if( !hasduplicates )
+            {
+                apserv.inc(ref monitor.sortedcnt, _params);
+                sortedcnt = monitor.sortedcnt;
+                apserv.rvectorgrowto(ref monitor.sortedstp, sortedcnt, _params);
+                apserv.ivectorgrowto(ref monitor.sortedidx, sortedcnt, _params);
+                monitor.sortedstp[sortedcnt-1] = stp;
+                monitor.sortedidx[sortedcnt-1] = enqueuedcnt-1;
+                for(i=sortedcnt-2; i>=0; i--)
+                {
+                    if( monitor.sortedstp[i]<=monitor.sortedstp[i+1] )
+                    {
+                        break;
+                    }
+                    v = monitor.sortedstp[i];
+                    monitor.sortedstp[i] = monitor.sortedstp[i+1];
+                    monitor.sortedstp[i+1] = v;
+                    j = monitor.sortedidx[i];
+                    monitor.sortedidx[i] = monitor.sortedidx[i+1];
+                    monitor.sortedidx[i+1] = j;
+                }
+            }
+            
+            //
+            // Scan sorted representation, check for C0 and C1 continuity
+            // violations.
+            //
+            apserv.rvectorsetlengthatleast(ref monitor.f, sortedcnt, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.g, sortedcnt*n, _params);
+            for(funcidx=0; funcidx<=k-1; funcidx++)
+            {
+                
+                //
+                // Fetch current function and its gradient to the contiguous storage
+                //
+                for(i=0; i<=sortedcnt-1; i++)
+                {
+                    monitor.f[i] = monitor.enqueuedfunc[monitor.sortedidx[i]*k+funcidx];
+                    for(j=0; j<=n-1; j++)
+                    {
+                        monitor.g[i*n+j] = monitor.enqueuedjac[monitor.sortedidx[i]*k+funcidx,j];
+                    }
+                }
+                
+                //
+                // Check C0 continuity.
+                //
+                // The basis approach is that we find appropriate candidate point
+                // (either a local minimum along the line - for target; or an interval
+                // where function sign is changed - for constraints), calculate left
+                // and right estimates of the Lipschitz constant (slopes between points
+                // #0 and #1, #2 and #3), and then calculate slope between points #1 and
+                // #2 and compare it with left/right estimates.
+                //
+                // The actual approach is a bit more complex to account for different
+                // sources of numerical noise and different false positive scenarios.
+                //
+                if( funcidx==0 )
+                {
+                    for(stpidx=0; stpidx<=sortedcnt-4; stpidx++)
+                    {
+                        f0 = monitor.f[stpidx+0];
+                        f1 = monitor.f[stpidx+1];
+                        f2 = monitor.f[stpidx+2];
+                        f3 = monitor.f[stpidx+3];
+                        noise0 = ognoiselevelf*Math.Max(Math.Abs(f0), 1.0);
+                        noise1 = ognoiselevelf*Math.Max(Math.Abs(f1), 1.0);
+                        noise2 = ognoiselevelf*Math.Max(Math.Abs(f2), 1.0);
+                        noise3 = ognoiselevelf*Math.Max(Math.Abs(f3), 1.0);
+                        if( !(f1<f0+(noise0+noise1) && f1<f2) )
+                        {
+                            continue;
+                        }
+                        testc0continuity(f0, f1, f2, f3, noise0, noise1, noise2, noise3, monitor.sortedstp[stpidx+1]-monitor.sortedstp[stpidx+0], monitor.sortedstp[stpidx+2]-monitor.sortedstp[stpidx+1], monitor.sortedstp[stpidx+3]-monitor.sortedstp[stpidx+2], true, ref rating, ref lipschitz, _params);
+                        if( rating>ogminrating0 && rating>monitor.nonc0currentrating )
+                        {
+                            monitor.nonc0currentrating = rating;
+                            monitor.rep.nonc0suspected = true;
+                            monitor.rep.nonc0test0positive = true;
+                            monitor.rep.nonc0lipschitzc = lipschitz;
+                            monitor.rep.nonc0fidx = funcidx;
+                        }
+                    }
+                }
+                
+                //
+                // C1 continuity test #0
+                //
+                for(stpidx=0; stpidx<=sortedcnt-7; stpidx++)
+                {
+                    
+                    //
+                    // Fetch function values
+                    //
+                    f2 = monitor.f[stpidx+2];
+                    f3 = monitor.f[stpidx+3];
+                    f4 = monitor.f[stpidx+4];
+                    noise2 = ognoiselevelf*Math.Max(Math.Abs(f2), 1.0);
+                    noise3 = ognoiselevelf*Math.Max(Math.Abs(f3), 1.0);
+                    
+                    //
+                    // Decide whether we want to test this interval or not; for target
+                    // function we test intervals around minimum, for constraints we
+                    // test intervals of sign change.
+                    //
+                    if( funcidx==0 )
+                    {
+                        
+                        //
+                        // Skip if not minimum
+                        //
+                        if( !(f3<f2+(noise2+noise3) && f3<f4) )
+                        {
+                            continue;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Skip if sign does not change
+                        //
+                        if( Math.Sign(f2*f4)>0 )
+                        {
+                            continue;
+                        }
+                    }
+                    c1continuitytest0(monitor, funcidx, stpidx+0, sortedcnt, _params);
+                    c1continuitytest0(monitor, funcidx, stpidx+1, sortedcnt, _params);
+                }
+                
+                //
+                // C1 continuity test #1
+                //
+                for(stpidx=0; stpidx<=sortedcnt-4; stpidx++)
+                {
+                    
+                    //
+                    // Fetch function values from the interval being tested
+                    //
+                    f0 = monitor.f[stpidx+0];
+                    f1 = monitor.f[stpidx+1];
+                    f2 = monitor.f[stpidx+2];
+                    f3 = monitor.f[stpidx+3];
+                    noise0 = ognoiselevelf*Math.Max(Math.Abs(f0), 1.0);
+                    noise1 = ognoiselevelf*Math.Max(Math.Abs(f1), 1.0);
+                    noise2 = ognoiselevelf*Math.Max(Math.Abs(f2), 1.0);
+                    noise3 = ognoiselevelf*Math.Max(Math.Abs(f3), 1.0);
+                    
+                    //
+                    // Decide whether we want to test this interval or not; for target
+                    // function we test intervals around minimum, for constraints we
+                    // test intervals of sign change.
+                    //
+                    if( funcidx==0 )
+                    {
+                        
+                        //
+                        // Skip if not minimum
+                        //
+                        if( !(f1<f0+(noise0+noise1) && f2<f3+noise2+noise3) )
+                        {
+                            continue;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Skip if sign does not change
+                        //
+                        if( Math.Sign(f0*f3)>0 )
+                        {
+                            continue;
+                        }
+                    }
+                    c1continuitytest1(monitor, funcidx, stpidx, sortedcnt, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine enqueues one more trial point for a task with scalar
+        function with unscaled variables - a convenience wrapper for more general
+        EnqueuePoint()
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void smoothnessmonitorenqueuepoint1u(smoothnessmonitor monitor,
+            double[] s,
+            double[] invs,
+            double[] d,
+            double stp,
+            double[] x,
+            double f0,
+            double[] j0,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int k = 0;
+            int i = 0;
+
+            n = monitor.n;
+            k = monitor.k;
+            if( !monitor.checksmoothness )
+            {
+                return;
+            }
+            alglib.ap.assert(k==1, "SmoothnessMonitorEnqueuePoint1: K<>1");
+            apserv.rvectorsetlengthatleast(ref monitor.xu, n, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.du, n, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.f0, 1, _params);
+            apserv.rmatrixsetlengthatleast(ref monitor.j0, 1, n, _params);
+            monitor.f0[0] = f0;
+            for(i=0; i<=n-1; i++)
+            {
+                monitor.xu[i] = x[i]*invs[i];
+                monitor.du[i] = d[i]*invs[i];
+                monitor.j0[0,i] = j0[i]*s[i];
+            }
+            smoothnessmonitorenqueuepoint(monitor, monitor.du, stp, monitor.xu, monitor.f0, monitor.j0, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine finalizes line search
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void smoothnessmonitorfinalizelinesearch(smoothnessmonitor monitor,
+            alglib.xparams _params)
+        {
+            
+            //
+            // As for now - nothing to be done.
+            //
+        }
+
+
+        /*************************************************************************
+        This subroutine exports report to user-readable representation (all arrays
+        are forced to have exactly same size as needed; unused arrays are  set  to
+        zero length).
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void smoothnessmonitorexportreport(smoothnessmonitor monitor,
+            optguardapi.optguardreport rep,
+            alglib.xparams _params)
+        {
+            
+            //
+            // Finalize last line search, just to be sure
+            //
+            if( monitor.enqueuedcnt>0 )
+            {
+                smoothnessmonitorfinalizelinesearch(monitor, _params);
+            }
+            
+            //
+            // Export report
+            //
+            optguardapi.optguardexportreport(monitor.rep, monitor.n, monitor.k, monitor.badgradhasxj, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Check numerical gradient at point X0 (unscaled variables!), with  optional
+        box constraints [BndL,BndU] (if  HasBoxConstraints=True)  and  with  scale
+        vector S[].
+
+        Step S[i]*TestStep is performed along I-th variable.
+
+        NeedFiJ rcomm protocol is used to request derivative information.
+
+        Box constraints BndL/BndU are expected to be feasible. It is  possible  to
+        have BndL=BndU.
+
+          -- ALGLIB --
+             Copyright 06.12.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool smoothnessmonitorcheckgradientatx0(smoothnessmonitor monitor,
+            double[] unscaledx0,
+            double[] s,
+            double[] bndl,
+            double[] bndu,
+            bool hasboxconstraints,
+            double teststep,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int k = 0;
+            int i = 0;
+            int j = 0;
+            int varidx = 0;
+            double v = 0;
+            double vp = 0;
+            double vm = 0;
+            double vc = 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( monitor.rstateg0.stage>=0 )
+            {
+                n = monitor.rstateg0.ia[0];
+                k = monitor.rstateg0.ia[1];
+                i = monitor.rstateg0.ia[2];
+                j = monitor.rstateg0.ia[3];
+                varidx = monitor.rstateg0.ia[4];
+                v = monitor.rstateg0.ra[0];
+                vp = monitor.rstateg0.ra[1];
+                vm = monitor.rstateg0.ra[2];
+                vc = monitor.rstateg0.ra[3];
+            }
+            else
+            {
+                n = 359;
+                k = -58;
+                i = -919;
+                j = -909;
+                varidx = 81;
+                v = 255;
+                vp = 74;
+                vm = -788;
+                vc = 809;
+            }
+            if( monitor.rstateg0.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( monitor.rstateg0.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( monitor.rstateg0.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( monitor.rstateg0.stage==3 )
+            {
+                goto lbl_3;
+            }
+            
+            //
+            // Routine body
+            //
+            n = monitor.n;
+            k = monitor.k;
+            monitor.needfij = false;
+            
+            //
+            // Quick exit
+            //
+            if( ((n<=0 || k<=0) || !math.isfinite(teststep)) || (double)(teststep)==(double)(0) )
+            {
+                result = false;
+                return result;
+            }
+            teststep = Math.Abs(teststep);
+            
+            //
+            // Allocate storage
+            //
+            apserv.rvectorsetlengthatleast(ref monitor.x, n, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.fi, k, _params);
+            apserv.rmatrixsetlengthatleast(ref monitor.j, k, n, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.xbase, n, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.fbase, k, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.fm, k, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.fc, k, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.fp, k, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.jm, k, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.jc, k, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.jp, k, _params);
+            apserv.rmatrixsetlengthatleast(ref monitor.jbaseusr, k, n, _params);
+            apserv.rmatrixsetlengthatleast(ref monitor.jbasenum, k, n, _params);
+            apserv.rvectorsetlengthatleast(ref monitor.rep.badgradxbase, n, _params);
+            apserv.rmatrixsetlengthatleast(ref monitor.rep.badgraduser, k, n, _params);
+            apserv.rmatrixsetlengthatleast(ref monitor.rep.badgradnum, k, n, _params);
+            
+            //
+            // Set XBase/Jacobian presence flag
+            //
+            monitor.badgradhasxj = true;
+            
+            //
+            // Determine reference point, compute function vector and user-supplied Jacobian
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                v = unscaledx0[i];
+                if( (hasboxconstraints && math.isfinite(bndl[i])) && (double)(v)<(double)(bndl[i]) )
+                {
+                    v = bndl[i];
+                }
+                if( (hasboxconstraints && math.isfinite(bndu[i])) && (double)(v)>(double)(bndu[i]) )
+                {
+                    v = bndu[i];
+                }
+                monitor.xbase[i] = v;
+                monitor.rep.badgradxbase[i] = v;
+                monitor.x[i] = v;
+            }
+            monitor.needfij = true;
+            monitor.rstateg0.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            monitor.needfij = false;
+            for(i=0; i<=k-1; i++)
+            {
+                monitor.fbase[i] = monitor.fi[i];
+                for(j=0; j<=n-1; j++)
+                {
+                    monitor.jbaseusr[i,j] = monitor.j[i,j];
+                    monitor.rep.badgraduser[i,j] = monitor.j[i,j];
+                }
+            }
+            
+            //
+            // Check Jacobian column by column
+            //
+            varidx = 0;
+        lbl_4:
+            if( varidx>n-1 )
+            {
+                goto lbl_6;
+            }
+            
+            //
+            // Determine test location.
+            //
+            v = monitor.xbase[varidx];
+            vm = v-s[varidx]*teststep;
+            vp = v+s[varidx]*teststep;
+            if( (hasboxconstraints && math.isfinite(bndl[varidx])) && (double)(vm)<(double)(bndl[varidx]) )
+            {
+                vm = bndl[varidx];
+            }
+            if( (hasboxconstraints && math.isfinite(bndu[varidx])) && (double)(vp)>(double)(bndu[varidx]) )
+            {
+                vp = bndu[varidx];
+            }
+            vc = vm+(vp-vm)/2;
+            
+            //
+            // Quickly skip fixed variables
+            //
+            if( ((double)(vm)==(double)(vp) || (double)(vc)==(double)(vm)) || (double)(vc)==(double)(vp) )
+            {
+                for(i=0; i<=k-1; i++)
+                {
+                    monitor.rep.badgradnum[i,varidx] = 0;
+                }
+                goto lbl_5;
+            }
+            
+            //
+            // Compute F/J at three trial points
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                monitor.x[i] = monitor.xbase[i];
+            }
+            monitor.x[varidx] = vm;
+            monitor.needfij = true;
+            monitor.rstateg0.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            monitor.needfij = false;
+            for(i=0; i<=k-1; i++)
+            {
+                monitor.fm[i] = monitor.fi[i];
+                monitor.jm[i] = monitor.j[i,varidx];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                monitor.x[i] = monitor.xbase[i];
+            }
+            monitor.x[varidx] = vc;
+            monitor.needfij = true;
+            monitor.rstateg0.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            monitor.needfij = false;
+            for(i=0; i<=k-1; i++)
+            {
+                monitor.fc[i] = monitor.fi[i];
+                monitor.jc[i] = monitor.j[i,varidx];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                monitor.x[i] = monitor.xbase[i];
+            }
+            monitor.x[varidx] = vp;
+            monitor.needfij = true;
+            monitor.rstateg0.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            monitor.needfij = false;
+            for(i=0; i<=k-1; i++)
+            {
+                monitor.fp[i] = monitor.fi[i];
+                monitor.jp[i] = monitor.j[i,varidx];
+            }
+            
+            //
+            // Check derivative
+            //
+            for(i=0; i<=k-1; i++)
+            {
+                monitor.rep.badgradnum[i,varidx] = (monitor.fp[i]-monitor.fm[i])/(vp-vm);
+                if( !derivativecheck(monitor.fm[i], monitor.jm[i]*s[varidx], monitor.fp[i], monitor.jp[i]*s[varidx], monitor.fc[i], monitor.jc[i]*s[varidx], (vp-vm)/s[varidx], _params) )
+                {
+                    monitor.rep.badgradsuspected = true;
+                    monitor.rep.badgradfidx = i;
+                    monitor.rep.badgradvidx = varidx;
+                }
+            }
+        lbl_5:
+            varidx = varidx+1;
+            goto lbl_4;
+        lbl_6:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            monitor.rstateg0.ia[0] = n;
+            monitor.rstateg0.ia[1] = k;
+            monitor.rstateg0.ia[2] = i;
+            monitor.rstateg0.ia[3] = j;
+            monitor.rstateg0.ia[4] = varidx;
+            monitor.rstateg0.ra[0] = v;
+            monitor.rstateg0.ra[1] = vp;
+            monitor.rstateg0.ra[2] = vm;
+            monitor.rstateg0.ra[3] = vc;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function calculates feasibility error (square root of sum of  squared
+        errors) for a Kx(NMain+NSlack) system of linear equalities.
+            
+        INPUT PARAMETERS:
+            CE      -   set of K equality constraints, array[K,NMain+NSlack+1]
+            X       -   candidate point, array [NMain+NSlack]
+            NMain   -   number of primary variables
+            NSlack  -   number of slack variables
+            K       -   number of constraints
+            Tmp0    -   possible preallocated buffer, automatically resized
+
+        RESULT:
+            Sqrt(SUM(Err^2))
+            
+          -- ALGLIB --
+             Copyright 17.09.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static double feasibilityerror(double[,] ce,
+            double[] x,
+            int nmain,
+            int nslack,
+            int k,
+            ref double[] tmp0,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int i = 0;
+
+            apserv.rvectorsetlengthatleast(ref tmp0, k, _params);
+            for(i=0; i<=k-1; i++)
+            {
+                tmp0[i] = -ce[i,nmain+nslack];
+            }
+            ablas.rmatrixgemv(k, nmain+nslack, 1.0, ce, 0, 0, 0, x, 0, 1.0, tmp0, 0, _params);
+            result = 0.0;
+            for(i=0; i<=k-1; i++)
+            {
+                result = result+tmp0[i]*tmp0[i];
+            }
+            result = Math.Sqrt(result);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function calculates feasibility error (square root of sum of  squared
+        errors) for a Kx(NMain+NSlack)  system  of  linear  equalities  and  error
+        gradient (with respect to x)
+            
+        INPUT PARAMETERS:
+            CE      -   set of K equality constraints, array[K,NMain+NSlack+1]
+            X       -   candidate point, array [NMain+NSlack]
+            NMain   -   number of primary variables
+            NSlack  -   number of slack variables
+            K       -   number of constraints
+            Grad    -   preallocated array[NMain+NSlack]
+            Tmp0    -   possible preallocated buffer, automatically resized
+
+        RESULT:
+            Err     -   Sqrt(SUM(Err^2))
+            Grad    -   error gradient with respect to X, array[NMain+NSlack]
+            
+          -- ALGLIB --
+             Copyright 17.09.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static void feasibilityerrorgrad(double[,] ce,
+            double[] x,
+            int nmain,
+            int nslack,
+            int k,
+            ref double err,
+            double[] grad,
+            ref double[] tmp0,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double v = 0;
+
+            err = 0;
+
+            alglib.ap.assert(alglib.ap.len(grad)>=nmain+nslack, "FeasibilityErrorGrad: integrity check failed");
+            apserv.rvectorsetlengthatleast(ref tmp0, k, _params);
+            ablas.rmatrixgemv(k, nmain+nslack, 1.0, ce, 0, 0, 0, x, 0, 0.0, tmp0, 0, _params);
+            err = 0.0;
+            for(i=0; i<=k-1; i++)
+            {
+                v = tmp0[i]-ce[i,nmain+nslack];
+                tmp0[i] = v;
+                err = err+v*v;
+            }
+            err = Math.Sqrt(err);
+            ablas.rmatrixgemv(nmain+nslack, k, 1.0, ce, 0, 0, 1, tmp0, 0, 0.0, grad, 0, _params);
+        }
+
+
+        /*************************************************************************
+        This  subroutine  checks  C0  continuity  and  returns  continuity  rating
+        (normalized value, with values above 50-500 being good indication  of  the
+        discontinuity) and Lipschitz constant.
+
+        An interval between F1 and F2 is  tested  for  (dis)continuity.  Per-point
+        noise estimates are provided. Delta[i] is a step from F[i] to F[i+1].
+
+        ApplySpecialCorrection parameter should be set to True  if  you  use  this
+        function to estimate continuity of  the  model  around  minimum;  it  adds
+        special correction which helps to detect "max(0,1/x)"-like discontinuities.
+        Without this correction algorithm will still work, but will be a bit  less
+        powerful. Do not use this correction  for  situations  when  you  want  to
+        estimate continuity around some non-extremal point  -  it  may  result  in
+        spurious discontinuities being reported.
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void testc0continuity(double f0,
+            double f1,
+            double f2,
+            double f3,
+            double noise0,
+            double noise1,
+            double noise2,
+            double noise3,
+            double delta0,
+            double delta1,
+            double delta2,
+            bool applyspecialcorrection,
+            ref double rating,
+            ref double lipschitz,
+            alglib.xparams _params)
+        {
+            double lipschitz01 = 0;
+            double lipschitz12 = 0;
+            double lipschitz23 = 0;
+
+            rating = 0;
+            lipschitz = 0;
+
+            
+            //
+            // Compute Lipschitz constant for the interval [0,1],
+            // add noise correction in order to get increased estimate (makes
+            // comparison below more conservative).
+            //
+            lipschitz01 = (Math.Abs(f1-f0)+(noise0+noise1))/delta0;
+            
+            //
+            // Compute Lipschitz constant for the interval [StpIdx+1,StpIdx+2],
+            // SUBTRACT noise correction in order to get decreased estimate (makes
+            // comparison below more conservative).
+            //
+            lipschitz12 = Math.Max(Math.Abs(f2-f1)-(noise1+noise2), 0.0)/delta1;
+            
+            //
+            // Compute Lipschitz constant for the interval [StpIdx+2,StpIdx+3]
+            // using special algorithm:
+            // a) if F3<F2-Noise23, Lipschitz constant is assumed to be zero
+            // b) otherwise, we compute Lipschitz constant as usual,
+            //    with noise correction term being added
+            //
+            // We need case (a) because some kinds of discontinuities
+            // (like one introduced by max(1/x,0)) manifest themselves
+            // in a quite special way.
+            //
+            if( applyspecialcorrection && f3<f2-(noise2+noise3) )
+            {
+                lipschitz23 = 0;
+            }
+            else
+            {
+                lipschitz23 = (Math.Abs(f3-f2)+(noise2+noise3))/delta2;
+            }
+            
+            //
+            // Compute rating (ratio of two Lipschitz constants)
+            //
+            alglib.ap.assert(Math.Max(lipschitz01, lipschitz23)>0, "OptGuard: integrity check failed");
+            rating = lipschitz12/Math.Max(lipschitz01, lipschitz23);
+            lipschitz = lipschitz12;
+        }
+
+
+        /*************************************************************************
+        This subroutine checks C1 continuity using test #0 (function  values  from
+        the line search log are studied, gradient is not used).
+
+        An interval between F[StpIdx+0] and F[StpIdx+5]is  tested for  continuity.
+        An normalized error metric (Lipschitz constant growth for the  derivative)
+        for the interval in question is calculated. Values above  50  are  a  good
+        indication of the discontinuity.
+
+        A six-point algorithm is used for testing, so we expect that Monitor.F and
+        Monitor.Stp have enough points for this test.
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void c1continuitytest0(smoothnessmonitor monitor,
+            int funcidx,
+            int stpidx,
+            int sortedcnt,
+            alglib.xparams _params)
+        {
+            double f0 = 0;
+            double f1 = 0;
+            double f2 = 0;
+            double f3 = 0;
+            double f4 = 0;
+            double f5 = 0;
+            double noise0 = 0;
+            double noise1 = 0;
+            double noise2 = 0;
+            double noise3 = 0;
+            double noise4 = 0;
+            double noise5 = 0;
+            double delta0 = 0;
+            double delta1 = 0;
+            double delta2 = 0;
+            double delta3 = 0;
+            double delta4 = 0;
+            double d0 = 0;
+            double d1 = 0;
+            double d2 = 0;
+            double d3 = 0;
+            double newnoise0 = 0;
+            double newnoise1 = 0;
+            double newnoise2 = 0;
+            double newnoise3 = 0;
+            double newdelta0 = 0;
+            double newdelta1 = 0;
+            double newdelta2 = 0;
+            double rating = 0;
+            double lipschitz = 0;
+            double lengthrating = 0;
+            int i = 0;
+            int n = 0;
+            double nrm = 0;
+
+            n = monitor.n;
+            alglib.ap.assert(stpidx+5<sortedcnt, "C1ContinuityTest0: integrity check failed");
+            alglib.ap.assert((double)(monitor.sortedstp[0])==(double)(0), "C1ContinuityTest0: integrity check failed");
+            alglib.ap.assert((double)(monitor.sortedstp[sortedcnt-1])>(double)(0), "C1ContinuityTest0: integrity check failed");
+            
+            //
+            // Fetch F, noise, Delta's
+            //
+            f0 = monitor.f[stpidx+0];
+            f1 = monitor.f[stpidx+1];
+            f2 = monitor.f[stpidx+2];
+            f3 = monitor.f[stpidx+3];
+            f4 = monitor.f[stpidx+4];
+            f5 = monitor.f[stpidx+5];
+            noise0 = ognoiselevelf*Math.Max(Math.Abs(f0), 1.0);
+            noise1 = ognoiselevelf*Math.Max(Math.Abs(f1), 1.0);
+            noise2 = ognoiselevelf*Math.Max(Math.Abs(f2), 1.0);
+            noise3 = ognoiselevelf*Math.Max(Math.Abs(f3), 1.0);
+            noise4 = ognoiselevelf*Math.Max(Math.Abs(f4), 1.0);
+            noise5 = ognoiselevelf*Math.Max(Math.Abs(f5), 1.0);
+            delta0 = monitor.sortedstp[stpidx+1]-monitor.sortedstp[stpidx+0];
+            delta1 = monitor.sortedstp[stpidx+2]-monitor.sortedstp[stpidx+1];
+            delta2 = monitor.sortedstp[stpidx+3]-monitor.sortedstp[stpidx+2];
+            delta3 = monitor.sortedstp[stpidx+4]-monitor.sortedstp[stpidx+3];
+            delta4 = monitor.sortedstp[stpidx+5]-monitor.sortedstp[stpidx+4];
+            
+            //
+            // Differentiate functions, get derivative values and noise
+            // estimates at points (0+1)/2, (1+2)/2, (3+4)/2, (3+4)/2,
+            // (4+5)/2. Compute new step values NewDelta[i] and new
+            // noise estimates.
+            //
+            d0 = (f1-f0)/delta0;
+            d1 = (f2-f1)/delta1;
+            d2 = (f4-f3)/delta3;
+            d3 = (f5-f4)/delta4;
+            newnoise0 = (noise0+noise1)/delta0;
+            newnoise1 = (noise1+noise2)/delta1;
+            newnoise2 = (noise3+noise4)/delta3;
+            newnoise3 = (noise4+noise5)/delta4;
+            newdelta0 = 0.5*(delta0+delta1);
+            newdelta1 = 0.5*delta1+delta2+0.5*delta3;
+            newdelta2 = 0.5*(delta3+delta4);
+            
+            //
+            // Test with C0 continuity tester. "Special correction" is
+            // turned off for this test.
+            //
+            testc0continuity(d0, d1, d2, d3, newnoise0, newnoise1, newnoise2, newnoise3, newdelta0, newdelta1, newdelta2, false, ref rating, ref lipschitz, _params);
+            
+            //
+            // Store results
+            //
+            if( rating>ogminrating1 )
+            {
+                
+                //
+                // Store to total report
+                //
+                monitor.rep.nonc1test0positive = true;
+                if( rating>monitor.nonc1currentrating )
+                {
+                    monitor.nonc1currentrating = rating;
+                    monitor.rep.nonc1suspected = true;
+                    monitor.rep.nonc1lipschitzc = lipschitz;
+                    monitor.rep.nonc1fidx = funcidx;
+                }
+                
+                //
+                // Store to "strongest" report
+                //
+                if( rating>monitor.nonc1test0strrating )
+                {
+                    monitor.nonc1test0strrating = rating;
+                    monitor.nonc1test0strrep.positive = true;
+                    monitor.nonc1test0strrep.fidx = funcidx;
+                    monitor.nonc1test0strrep.n = n;
+                    monitor.nonc1test0strrep.cnt = sortedcnt;
+                    monitor.nonc1test0strrep.stpidxa = stpidx+1;
+                    monitor.nonc1test0strrep.stpidxb = stpidx+4;
+                    apserv.rvectorsetlengthatleast(ref monitor.nonc1test0strrep.x0, n, _params);
+                    apserv.rvectorsetlengthatleast(ref monitor.nonc1test0strrep.d, n, _params);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        monitor.nonc1test0strrep.x0[i] = monitor.enqueuedx[monitor.sortedidx[0]*n+i];
+                        monitor.nonc1test0strrep.d[i] = monitor.dcur[i];
+                    }
+                    apserv.rvectorsetlengthatleast(ref monitor.nonc1test0strrep.stp, sortedcnt, _params);
+                    apserv.rvectorsetlengthatleast(ref monitor.nonc1test0strrep.f, sortedcnt, _params);
+                    for(i=0; i<=sortedcnt-1; i++)
+                    {
+                        monitor.nonc1test0strrep.stp[i] = monitor.sortedstp[i];
+                        monitor.nonc1test0strrep.f[i] = monitor.f[i];
+                    }
+                }
+                
+                //
+                // Store to "longest" report
+                //
+                nrm = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    nrm = nrm+math.sqr(monitor.enqueuedx[monitor.sortedidx[0]*n+i]-monitor.enqueuedx[monitor.sortedidx[sortedcnt-1]*n+i]);
+                }
+                nrm = Math.Sqrt(nrm);
+                nrm = Math.Min(nrm, 1.0);
+                nrm = apserv.coalesce(nrm, math.machineepsilon, _params);
+                lengthrating = sortedcnt+Math.Log(nrm)/Math.Log(100);
+                if( lengthrating>monitor.nonc1test0lngrating )
+                {
+                    monitor.nonc1test0lngrating = lengthrating;
+                    monitor.nonc1test0lngrep.positive = true;
+                    monitor.nonc1test0lngrep.fidx = funcidx;
+                    monitor.nonc1test0lngrep.n = n;
+                    monitor.nonc1test0lngrep.cnt = sortedcnt;
+                    monitor.nonc1test0lngrep.stpidxa = stpidx+1;
+                    monitor.nonc1test0lngrep.stpidxb = stpidx+4;
+                    apserv.rvectorsetlengthatleast(ref monitor.nonc1test0lngrep.x0, n, _params);
+                    apserv.rvectorsetlengthatleast(ref monitor.nonc1test0lngrep.d, n, _params);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        monitor.nonc1test0lngrep.x0[i] = monitor.enqueuedx[monitor.sortedidx[0]*n+i];
+                        monitor.nonc1test0lngrep.d[i] = monitor.dcur[i];
+                    }
+                    apserv.rvectorsetlengthatleast(ref monitor.nonc1test0lngrep.stp, sortedcnt, _params);
+                    apserv.rvectorsetlengthatleast(ref monitor.nonc1test0lngrep.f, sortedcnt, _params);
+                    for(i=0; i<=sortedcnt-1; i++)
+                    {
+                        monitor.nonc1test0lngrep.stp[i] = monitor.sortedstp[i];
+                        monitor.nonc1test0lngrep.f[i] = monitor.f[i];
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This  subroutine checks C1 continuity using test #1  (individual  gradient
+        components from the line search log are studied for continuity).
+
+        An interval between F[StpIdx+0] and F[StpIdx+3]is  tested for  continuity.
+        An normalized error metric (Lipschitz constant growth for the  derivative)
+        for the interval in question is calculated. Values above  50  are  a  good
+        indication of the discontinuity.
+
+          -- ALGLIB --
+             Copyright 19.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void c1continuitytest1(smoothnessmonitor monitor,
+            int funcidx,
+            int stpidx,
+            int sortedcnt,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int varidx = 0;
+            int n = 0;
+            double f0 = 0;
+            double f1 = 0;
+            double f2 = 0;
+            double f3 = 0;
+            double noise0 = 0;
+            double noise1 = 0;
+            double noise2 = 0;
+            double noise3 = 0;
+            double nrm = 0;
+            double rating = 0;
+            double lengthrating = 0;
+            double lipschitz = 0;
+
+            n = monitor.n;
+            alglib.ap.assert(stpidx+3<sortedcnt, "C1ContinuityTest1: integrity check failed");
+            alglib.ap.assert((double)(monitor.sortedstp[0])==(double)(0), "C1ContinuityTest1: integrity check failed");
+            alglib.ap.assert((double)(monitor.sortedstp[sortedcnt-1])>(double)(0), "C1ContinuityTest1: integrity check failed");
+            
+            //
+            // Study each component of the gradient in the interval in question
+            //
+            for(varidx=0; varidx<=n-1; varidx++)
+            {
+                f0 = monitor.g[(stpidx+0)*n+varidx];
+                f1 = monitor.g[(stpidx+1)*n+varidx];
+                f2 = monitor.g[(stpidx+2)*n+varidx];
+                f3 = monitor.g[(stpidx+3)*n+varidx];
+                noise0 = ognoiselevelg*Math.Max(Math.Abs(f0), 1.0);
+                noise1 = ognoiselevelg*Math.Max(Math.Abs(f1), 1.0);
+                noise2 = ognoiselevelg*Math.Max(Math.Abs(f2), 1.0);
+                noise3 = ognoiselevelg*Math.Max(Math.Abs(f3), 1.0);
+                testc0continuity(f0, f1, f2, f3, noise0, noise1, noise2, noise3, monitor.sortedstp[stpidx+1]-monitor.sortedstp[stpidx+0], monitor.sortedstp[stpidx+2]-monitor.sortedstp[stpidx+1], monitor.sortedstp[stpidx+3]-monitor.sortedstp[stpidx+2], false, ref rating, ref lipschitz, _params);
+                
+                //
+                // Store results
+                //
+                if( rating>ogminrating1 )
+                {
+                    
+                    //
+                    // Store to total report
+                    //
+                    monitor.rep.nonc1test1positive = true;
+                    if( rating>monitor.nonc1currentrating )
+                    {
+                        monitor.nonc1currentrating = rating;
+                        monitor.rep.nonc1suspected = true;
+                        monitor.rep.nonc1lipschitzc = lipschitz;
+                        monitor.rep.nonc1fidx = funcidx;
+                    }
+                    
+                    //
+                    // Store to "strongest" report
+                    //
+                    if( rating>monitor.nonc1test1strrating )
+                    {
+                        monitor.nonc1test1strrating = rating;
+                        monitor.nonc1test1strrep.positive = true;
+                        monitor.nonc1test1strrep.fidx = funcidx;
+                        monitor.nonc1test1strrep.vidx = varidx;
+                        monitor.nonc1test1strrep.n = n;
+                        monitor.nonc1test1strrep.cnt = sortedcnt;
+                        monitor.nonc1test1strrep.stpidxa = stpidx+0;
+                        monitor.nonc1test1strrep.stpidxb = stpidx+3;
+                        apserv.rvectorsetlengthatleast(ref monitor.nonc1test1strrep.x0, n, _params);
+                        apserv.rvectorsetlengthatleast(ref monitor.nonc1test1strrep.d, n, _params);
+                        for(i=0; i<=n-1; i++)
+                        {
+                            monitor.nonc1test1strrep.x0[i] = monitor.enqueuedx[monitor.sortedidx[0]*n+i];
+                            monitor.nonc1test1strrep.d[i] = monitor.dcur[i];
+                        }
+                        apserv.rvectorsetlengthatleast(ref monitor.nonc1test1strrep.stp, sortedcnt, _params);
+                        apserv.rvectorsetlengthatleast(ref monitor.nonc1test1strrep.g, sortedcnt, _params);
+                        for(i=0; i<=sortedcnt-1; i++)
+                        {
+                            monitor.nonc1test1strrep.stp[i] = monitor.sortedstp[i];
+                            monitor.nonc1test1strrep.g[i] = monitor.g[i*n+varidx];
+                        }
+                    }
+                    
+                    //
+                    // Store to "longest" report
+                    //
+                    nrm = 0;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        nrm = nrm+math.sqr(monitor.enqueuedx[monitor.sortedidx[0]*n+i]-monitor.enqueuedx[monitor.sortedidx[sortedcnt-1]*n+i]);
+                    }
+                    nrm = Math.Sqrt(nrm);
+                    nrm = Math.Min(nrm, 1.0);
+                    nrm = apserv.coalesce(nrm, math.machineepsilon, _params);
+                    lengthrating = sortedcnt+Math.Log(nrm)/Math.Log(100);
+                    if( lengthrating>monitor.nonc1test1lngrating )
+                    {
+                        monitor.nonc1test1lngrating = lengthrating;
+                        monitor.nonc1test1lngrep.positive = true;
+                        monitor.nonc1test1lngrep.fidx = funcidx;
+                        monitor.nonc1test1lngrep.vidx = varidx;
+                        monitor.nonc1test1lngrep.n = n;
+                        monitor.nonc1test1lngrep.cnt = sortedcnt;
+                        monitor.nonc1test1lngrep.stpidxa = stpidx+0;
+                        monitor.nonc1test1lngrep.stpidxb = stpidx+3;
+                        apserv.rvectorsetlengthatleast(ref monitor.nonc1test1lngrep.x0, n, _params);
+                        apserv.rvectorsetlengthatleast(ref monitor.nonc1test1lngrep.d, n, _params);
+                        for(i=0; i<=n-1; i++)
+                        {
+                            monitor.nonc1test1lngrep.x0[i] = monitor.enqueuedx[monitor.sortedidx[0]*n+i];
+                            monitor.nonc1test1lngrep.d[i] = monitor.dcur[i];
+                        }
+                        apserv.rvectorsetlengthatleast(ref monitor.nonc1test1lngrep.stp, sortedcnt, _params);
+                        apserv.rvectorsetlengthatleast(ref monitor.nonc1test1lngrep.g, sortedcnt, _params);
+                        for(i=0; i<=sortedcnt-1; i++)
+                        {
+                            monitor.nonc1test1lngrep.stp[i] = monitor.sortedstp[i];
+                            monitor.nonc1test1lngrep.g[i] = monitor.g[i*n+varidx];
+                        }
+                    }
+                }
+            }
+        }
+
+
+    }
+    public class snnls
+    {
+        /*************************************************************************
+        This structure is a SNNLS (Specialized Non-Negative Least Squares) solver.
+
+        It solves problems of the form |A*x-b|^2 => min subject to  non-negativity
+        constraints on SOME components of x, with structured A (first  NS  columns
+        are just unit matrix, next ND columns store dense part).
+
+        This solver is suited for solution of many sequential NNLS  subproblems  -
+        it keeps track of previously allocated memory and reuses  it  as  much  as
+        possible.
+        *************************************************************************/
+        public class snnlssolver : apobject
+        {
+            public int ns;
+            public int nd;
+            public int nr;
+            public double[,] densea;
+            public double[] b;
+            public bool[] nnc;
+            public double debugflops;
+            public int debugmaxinnerits;
+            public double[] xn;
+            public double[] xp;
+            public double[,] tmpca;
+            public double[,] tmplq;
+            public double[,] trda;
+            public double[] trdd;
+            public double[] crb;
+            public double[] g;
+            public double[] d;
+            public double[] dx;
+            public double[] diagaa;
+            public double[] cb;
+            public double[] cx;
+            public double[] cborg;
+            public double[] tmpcholesky;
+            public double[] r;
+            public double[] regdiag;
+            public double[] tmp0;
+            public double[] tmp1;
+            public double[] tmp2;
+            public int[] rdtmprowmap;
+            public snnlssolver()
+            {
+                init();
+            }
+            public override void init()
+            {
+                densea = new double[0,0];
+                b = new double[0];
+                nnc = new bool[0];
+                xn = new double[0];
+                xp = new double[0];
+                tmpca = new double[0,0];
+                tmplq = new double[0,0];
+                trda = new double[0,0];
+                trdd = new double[0];
+                crb = new double[0];
+                g = new double[0];
+                d = new double[0];
+                dx = new double[0];
+                diagaa = new double[0];
+                cb = new double[0];
+                cx = new double[0];
+                cborg = new double[0];
+                tmpcholesky = new double[0];
+                r = new double[0];
+                regdiag = new double[0];
+                tmp0 = new double[0];
+                tmp1 = new double[0];
+                tmp2 = new double[0];
+                rdtmprowmap = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                snnlssolver _result = new snnlssolver();
+                _result.ns = ns;
+                _result.nd = nd;
+                _result.nr = nr;
+                _result.densea = (double[,])densea.Clone();
+                _result.b = (double[])b.Clone();
+                _result.nnc = (bool[])nnc.Clone();
+                _result.debugflops = debugflops;
+                _result.debugmaxinnerits = debugmaxinnerits;
+                _result.xn = (double[])xn.Clone();
+                _result.xp = (double[])xp.Clone();
+                _result.tmpca = (double[,])tmpca.Clone();
+                _result.tmplq = (double[,])tmplq.Clone();
+                _result.trda = (double[,])trda.Clone();
+                _result.trdd = (double[])trdd.Clone();
+                _result.crb = (double[])crb.Clone();
+                _result.g = (double[])g.Clone();
+                _result.d = (double[])d.Clone();
+                _result.dx = (double[])dx.Clone();
+                _result.diagaa = (double[])diagaa.Clone();
+                _result.cb = (double[])cb.Clone();
+                _result.cx = (double[])cx.Clone();
+                _result.cborg = (double[])cborg.Clone();
+                _result.tmpcholesky = (double[])tmpcholesky.Clone();
+                _result.r = (double[])r.Clone();
+                _result.regdiag = (double[])regdiag.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmp1 = (double[])tmp1.Clone();
+                _result.tmp2 = (double[])tmp2.Clone();
+                _result.rdtmprowmap = (int[])rdtmprowmap.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        This subroutine is used to initialize SNNLS solver.
+
+        By default, empty NNLS problem is produced, but we allocated enough  space
+        to store problems with NSMax+NDMax columns and  NRMax  rows.  It  is  good
+        place to provide algorithm with initial estimate of the space requirements,
+        although you may underestimate problem size or even pass zero estimates  -
+        in this case buffer variables will be resized automatically  when  you set
+        NNLS problem.
+
+        Previously allocated buffer variables are reused as much as possible. This
+        function does not clear structure completely, it tries to preserve as much
+        dynamically allocated memory as possible.
+
+          -- ALGLIB --
+             Copyright 10.10.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void snnlsinit(int nsmax,
+            int ndmax,
+            int nrmax,
+            snnlssolver s,
+            alglib.xparams _params)
+        {
+            s.ns = 0;
+            s.nd = 0;
+            s.nr = 0;
+            apserv.rmatrixsetlengthatleast(ref s.densea, nrmax, ndmax, _params);
+            apserv.rmatrixsetlengthatleast(ref s.tmpca, nrmax, ndmax, _params);
+            apserv.rvectorsetlengthatleast(ref s.b, nrmax, _params);
+            apserv.bvectorsetlengthatleast(ref s.nnc, nsmax+ndmax, _params);
+            s.debugflops = 0.0;
+            s.debugmaxinnerits = 0;
+        }
+
+
+        /*************************************************************************
+        This subroutine is used to set NNLS problem:
+
+                ( [ 1     |      ]   [   ]   [   ] )^2
+                ( [   1   |      ]   [   ]   [   ] )
+            min ( [     1 |  Ad  ] * [ x ] - [ b ] )    s.t. x>=0
+                ( [       |      ]   [   ]   [   ] )
+                ( [       |      ]   [   ]   [   ] )
+
+        where:
+        * identity matrix has NS*NS size (NS<=NR, NS can be zero)
+        * dense matrix Ad has NR*ND size
+        * b is NR*1 vector
+        * x is (NS+ND)*1 vector
+        * all elements of x are non-negative (this constraint can be removed later
+          by calling SNNLSDropNNC() function)
+
+        Previously allocated buffer variables are reused as much as possible.
+        After you set problem, you can solve it with SNNLSSolve().
+
+        INPUT PARAMETERS:
+            S   -   SNNLS solver, must be initialized with SNNLSInit() call
+            A   -   array[NR,ND], dense part of the system
+            B   -   array[NR], right part
+            NS  -   size of the sparse part of the system, 0<=NS<=NR
+            ND  -   size of the dense part of the system, ND>=0
+            NR  -   rows count, NR>0
+
+        NOTE:
+            1. You can have NS+ND=0, solver will correctly accept such combination
+               and return empty array as problem solution.
+            
+          -- ALGLIB --
+             Copyright 10.10.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void snnlssetproblem(snnlssolver s,
+            double[,] a,
+            double[] b,
+            int ns,
+            int nd,
+            int nr,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(nd>=0, "SNNLSSetProblem: ND<0");
+            alglib.ap.assert(ns>=0, "SNNLSSetProblem: NS<0");
+            alglib.ap.assert(nr>0, "SNNLSSetProblem: NR<=0");
+            alglib.ap.assert(ns<=nr, "SNNLSSetProblem: NS>NR");
+            alglib.ap.assert(alglib.ap.rows(a)>=nr || nd==0, "SNNLSSetProblem: rows(A)<NR");
+            alglib.ap.assert(alglib.ap.cols(a)>=nd, "SNNLSSetProblem: cols(A)<ND");
+            alglib.ap.assert(alglib.ap.len(b)>=nr, "SNNLSSetProblem: length(B)<NR");
+            alglib.ap.assert(apserv.apservisfinitematrix(a, nr, nd, _params), "SNNLSSetProblem: A contains INF/NAN");
+            alglib.ap.assert(apserv.isfinitevector(b, nr, _params), "SNNLSSetProblem: B contains INF/NAN");
+            
+            //
+            // Copy problem
+            //
+            s.ns = ns;
+            s.nd = nd;
+            s.nr = nr;
+            if( nd>0 )
+            {
+                apserv.rmatrixsetlengthatleast(ref s.densea, nr, nd, _params);
+                for(i=0; i<=nr-1; i++)
+                {
+                    for(i_=0; i_<=nd-1;i_++)
+                    {
+                        s.densea[i,i_] = a[i,i_];
+                    }
+                }
+            }
+            apserv.rvectorsetlengthatleast(ref s.b, nr, _params);
+            for(i_=0; i_<=nr-1;i_++)
+            {
+                s.b[i_] = b[i_];
+            }
+            apserv.bvectorsetlengthatleast(ref s.nnc, ns+nd, _params);
+            for(i=0; i<=ns+nd-1; i++)
+            {
+                s.nnc[i] = true;
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine drops non-negativity constraint from the  problem  set  by
+        SNNLSSetProblem() call. This function must be called AFTER problem is set,
+        because each SetProblem() call resets constraints to their  default  state
+        (all constraints are present).
+
+        INPUT PARAMETERS:
+            S   -   SNNLS solver, must be initialized with SNNLSInit() call,
+                    problem must be set with SNNLSSetProblem() call.
+            Idx -   constraint index, 0<=IDX<NS+ND
+            
+          -- ALGLIB --
+             Copyright 10.10.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void snnlsdropnnc(snnlssolver s,
+            int idx,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(idx>=0, "SNNLSDropNNC: Idx<0");
+            alglib.ap.assert(idx<s.ns+s.nd, "SNNLSDropNNC: Idx>=NS+ND");
+            s.nnc[idx] = false;
+        }
+
+
+        /*************************************************************************
+        This subroutine is used to solve NNLS problem.
+
+        INPUT PARAMETERS:
+            S   -   SNNLS solver, must be initialized with SNNLSInit() call and
+                    problem must be set up with SNNLSSetProblem() call.
+            X   -   possibly preallocated buffer, automatically resized if needed
+
+        OUTPUT PARAMETERS:
+            X   -   array[NS+ND], solution
+            
+        NOTE:
+            1. You can have NS+ND=0, solver will correctly accept such combination
+               and return empty array as problem solution.
+            
+            2. Internal field S.DebugFLOPS contains rough estimate of  FLOPs  used
+               to solve problem. It can be used for debugging purposes. This field
+               is real-valued.
+            
+          -- ALGLIB --
+             Copyright 10.10.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void snnlssolve(snnlssolver s,
+            ref double[] x,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int ns = 0;
+            int nd = 0;
+            int nr = 0;
+            bool wasactivation = new bool();
+            double lambdav = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v = 0;
+            int outerits = 0;
+            int innerits = 0;
+            int maxouterits = 0;
+            double xtol = 0;
+            double kicklength = 0;
+            bool kickneeded = new bool();
+            double f0 = 0;
+            double f1 = 0;
+            double dnrm = 0;
+            int actidx = 0;
+            double stp = 0;
+            double stpmax = 0;
+
+            
+            //
+            // Prepare
+            //
+            ns = s.ns;
+            nd = s.nd;
+            nr = s.nr;
+            s.debugflops = 0.0;
+            
+            //
+            // Handle special cases:
+            // * NS+ND=0
+            // * ND=0
+            //
+            if( ns+nd==0 )
+            {
+                return;
+            }
+            if( nd==0 )
+            {
+                apserv.rvectorsetlengthatleast(ref x, ns, _params);
+                for(i=0; i<=ns-1; i++)
+                {
+                    x[i] = s.b[i];
+                    if( s.nnc[i] )
+                    {
+                        x[i] = Math.Max(x[i], 0.0);
+                    }
+                }
+                return;
+            }
+            
+            //
+            // Main cycle of BLEIC-SNNLS algorithm.
+            // Below we assume that ND>0.
+            //
+            apserv.rvectorsetlengthatleast(ref x, ns+nd, _params);
+            apserv.rvectorsetlengthatleast(ref s.xn, ns+nd, _params);
+            apserv.rvectorsetlengthatleast(ref s.xp, ns+nd, _params);
+            apserv.rvectorsetlengthatleast(ref s.g, ns+nd, _params);
+            apserv.rvectorsetlengthatleast(ref s.d, ns+nd, _params);
+            apserv.rvectorsetlengthatleast(ref s.r, nr, _params);
+            apserv.rvectorsetlengthatleast(ref s.diagaa, nd, _params);
+            apserv.rvectorsetlengthatleast(ref s.regdiag, ns+nd, _params);
+            apserv.rvectorsetlengthatleast(ref s.dx, ns+nd, _params);
+            for(i=0; i<=ns+nd-1; i++)
+            {
+                x[i] = 0.0;
+                s.regdiag[i] = 1.0;
+            }
+            lambdav = 1.0E6*math.machineepsilon;
+            maxouterits = 10;
+            outerits = 0;
+            innerits = 0;
+            xtol = 1.0E3*math.machineepsilon;
+            kicklength = Math.Sqrt(math.minrealnumber);
+            while( true )
+            {
+                
+                //
+                // Initial check for correctness of X
+                //
+                for(i=0; i<=ns+nd-1; i++)
+                {
+                    alglib.ap.assert(!s.nnc[i] || (double)(x[i])>=(double)(0), "SNNLS: integrity check failed");
+                }
+                
+                //
+                // Calculate gradient G and constrained descent direction D
+                //
+                funcgradu(s, x, s.r, s.g, ref f0, _params);
+                for(i=0; i<=ns+nd-1; i++)
+                {
+                    if( (s.nnc[i] && (double)(x[i])==(double)(0)) && (double)(s.g[i])>(double)(0) )
+                    {
+                        s.d[i] = 0.0;
+                    }
+                    else
+                    {
+                        s.d[i] = -s.g[i];
+                    }
+                }
+                
+                //
+                // Decide whether we need "kick" stage: special stage
+                // that moves us away from boundary constraints which are
+                // not strictly active (i.e. such constraints that x[i]=0.0 and d[i]>0).
+                //
+                // If we need kick stage, we make a kick - and restart iteration.
+                // If not, after this block we can rely on the fact that
+                // for all x[i]=0.0 we have d[i]=0.0
+                //
+                // NOTE: we do not increase outer iterations counter here
+                //
+                kickneeded = false;
+                for(i=0; i<=ns+nd-1; i++)
+                {
+                    if( (s.nnc[i] && (double)(x[i])==(double)(0.0)) && (double)(s.d[i])>(double)(0.0) )
+                    {
+                        kickneeded = true;
+                    }
+                }
+                if( kickneeded )
+                {
+                    
+                    //
+                    // Perform kick.
+                    // Restart.
+                    // Do not increase iterations counter.
+                    //
+                    for(i=0; i<=ns+nd-1; i++)
+                    {
+                        if( (double)(x[i])==(double)(0.0) && (double)(s.d[i])>(double)(0.0) )
+                        {
+                            x[i] = x[i]+kicklength;
+                        }
+                    }
+                    continue;
+                }
+                
+                //
+                // Newton phase
+                // Reduce problem to constrained triangular form and perform Newton
+                // steps with quick activation of constrants  (triangular  form  is
+                // updated in order to handle changed constraints).
+                //
+                for(i=0; i<=ns+nd-1; i++)
+                {
+                    s.xp[i] = x[i];
+                }
+                trdprepare(s, x, s.regdiag, lambdav, ref s.trdd, ref s.trda, ref s.tmp0, ref s.tmp1, ref s.tmp2, ref s.tmplq, _params);
+                while( true )
+                {
+                    
+                    //
+                    // Skip if debug limit on inner iterations count is turned on.
+                    //
+                    if( s.debugmaxinnerits>0 && innerits>=s.debugmaxinnerits )
+                    {
+                        break;
+                    }
+                    
+                    //
+                    // Prepare step vector.
+                    //
+                    funcgradu(s, x, s.r, s.g, ref f0, _params);
+                    for(i=0; i<=ns+nd-1; i++)
+                    {
+                        s.d[i] = -s.g[i];
+                        if( s.nnc[i] && (double)(x[i])==(double)(0.0) )
+                        {
+                            s.d[i] = 0.0;
+                        }
+                    }
+                    trdsolve(s.trdd, s.trda, ns, nd, s.d, _params);
+                    
+                    //
+                    // Perform unconstrained trial step and compare function values.
+                    //
+                    for(i=0; i<=ns+nd-1; i++)
+                    {
+                        s.xn[i] = x[i]+s.d[i];
+                    }
+                    func(s, s.xn, ref f1, _params);
+                    if( (double)(f1)>=(double)(f0) )
+                    {
+                        break;
+                    }
+                    
+                    //
+                    // Calculate length of D, maximum step and component which is
+                    // activated by this step. Break if D is exactly zero.
+                    //
+                    dnrm = 0.0;
+                    for(i=0; i<=ns+nd-1; i++)
+                    {
+                        dnrm = dnrm+math.sqr(s.d[i]);
+                    }
+                    dnrm = Math.Sqrt(dnrm);
+                    actidx = -1;
+                    stpmax = 1.0E50;
+                    for(i=0; i<=ns+nd-1; i++)
+                    {
+                        if( s.nnc[i] && (double)(s.d[i])<(double)(0.0) )
+                        {
+                            v = stpmax;
+                            stpmax = apserv.safeminposrv(x[i], -s.d[i], stpmax, _params);
+                            if( (double)(stpmax)<(double)(v) )
+                            {
+                                actidx = i;
+                            }
+                        }
+                    }
+                    if( (double)(dnrm)==(double)(0.0) )
+                    {
+                        break;
+                    }
+                    
+                    //
+                    // Perform constrained step and update X
+                    // and triangular model.
+                    //
+                    stp = Math.Min(1.0, stpmax);
+                    for(i=0; i<=ns+nd-1; i++)
+                    {
+                        v = x[i]+stp*s.d[i];
+                        if( s.nnc[i] )
+                        {
+                            v = Math.Max(v, 0.0);
+                        }
+                        s.xn[i] = v;
+                    }
+                    if( (double)(stp)==(double)(stpmax) && actidx>=0 )
+                    {
+                        s.xn[actidx] = 0.0;
+                    }
+                    wasactivation = false;
+                    for(i=0; i<=ns+nd-1; i++)
+                    {
+                        if( (double)(s.xn[i])==(double)(0.0) && (double)(x[i])!=(double)(0.0) )
+                        {
+                            wasactivation = true;
+                            trdfixvariable(s.trdd, s.trda, ns, nd, i, ref s.tmpcholesky, _params);
+                        }
+                    }
+                    for(i=0; i<=ns+nd-1; i++)
+                    {
+                        x[i] = s.xn[i];
+                    }
+                    
+                    //
+                    // Increment iterations counter.
+                    // Terminate if no constraint was activated.
+                    //
+                    apserv.inc(ref innerits, _params);
+                    if( !wasactivation )
+                    {
+                        break;
+                    }
+                }
+                
+                //
+                // Update outer iterations counter.
+                //
+                // Break if necessary:
+                // * maximum number of outer iterations performed
+                // * relative change in X is small enough
+                //
+                apserv.inc(ref outerits, _params);
+                if( outerits>=maxouterits )
+                {
+                    break;
+                }
+                v = 0;
+                for(i=0; i<=ns+nd-1; i++)
+                {
+                    v0 = Math.Abs(s.xp[i]);
+                    v1 = Math.Abs(x[i]);
+                    if( (double)(v0)!=(double)(0) || (double)(v1)!=(double)(0) )
+                    {
+                        v = Math.Max(v, Math.Abs(x[i]-s.xp[i])/Math.Max(v0, v1));
+                    }
+                }
+                if( (double)(v)<=(double)(xtol) )
+                {
+                    break;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates:
+        * residual vector R = A*x-b
+        * unconstrained gradient vector G
+        * function value F = 0.5*|R|^2
+
+        R and G must have at least N elements.
+
+          -- ALGLIB --
+             Copyright 15.07.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static void funcgradu(snnlssolver s,
+            double[] x,
+            double[] r,
+            double[] g,
+            ref double f,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int nr = 0;
+            int nd = 0;
+            int ns = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            f = 0;
+
+            nr = s.nr;
+            nd = s.nd;
+            ns = s.ns;
+            f = 0.0;
+            for(i=0; i<=nr-1; i++)
+            {
+                i1_ = (ns)-(0);
+                v = 0.0;
+                for(i_=0; i_<=nd-1;i_++)
+                {
+                    v += s.densea[i,i_]*x[i_+i1_];
+                }
+                if( i<ns )
+                {
+                    v = v+x[i];
+                }
+                v = v-s.b[i];
+                r[i] = v;
+                f = f+0.5*v*v;
+            }
+            for(i=0; i<=ns-1; i++)
+            {
+                g[i] = r[i];
+            }
+            for(i=ns; i<=ns+nd-1; i++)
+            {
+                g[i] = 0.0;
+            }
+            for(i=0; i<=nr-1; i++)
+            {
+                v = r[i];
+                i1_ = (0) - (ns);
+                for(i_=ns; i_<=ns+nd-1;i_++)
+                {
+                    g[i_] = g[i_] + v*s.densea[i,i_+i1_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates function value F = 0.5*|R|^2 at X.
+
+          -- ALGLIB --
+             Copyright 15.07.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static void func(snnlssolver s,
+            double[] x,
+            ref double f,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int nr = 0;
+            int nd = 0;
+            int ns = 0;
+            double v = 0;
+            int i_ = 0;
+            int i1_ = 0;
+
+            f = 0;
+
+            nr = s.nr;
+            nd = s.nd;
+            ns = s.ns;
+            f = 0.0;
+            for(i=0; i<=nr-1; i++)
+            {
+                i1_ = (ns)-(0);
+                v = 0.0;
+                for(i_=0; i_<=nd-1;i_++)
+                {
+                    v += s.densea[i,i_]*x[i_+i1_];
+                }
+                if( i<ns )
+                {
+                    v = v+x[i];
+                }
+                v = v-s.b[i];
+                f = f+0.5*v*v;
+            }
+        }
+
+
+        private static void trdprepare(snnlssolver s,
+            double[] x,
+            double[] diag,
+            double lambdav,
+            ref double[] trdd,
+            ref double[,] trda,
+            ref double[] tmp0,
+            ref double[] tmp1,
+            ref double[] tmp2,
+            ref double[,] tmplq,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int ns = 0;
+            int nd = 0;
+            int nr = 0;
+            double v = 0;
+            double cs = 0;
+            double sn = 0;
+            double r = 0;
+
+            
+            //
+            // Prepare
+            //
+            ns = s.ns;
+            nd = s.nd;
+            nr = s.nr;
+            
+            //
+            // Triangular reduction
+            //
+            apserv.rvectorsetlengthatleast(ref trdd, ns, _params);
+            apserv.rmatrixsetlengthatleast(ref trda, ns+nd, nd, _params);
+            apserv.rmatrixsetlengthatleast(ref tmplq, nd, nr+nd, _params);
+            for(i=0; i<=ns-1; i++)
+            {
+                
+                //
+                // Apply rotation to I-th row and corresponding row of
+                // regularizer. Here V is diagonal element of I-th row,
+                // which is set to 1.0 or 0.0 depending on variable
+                // status (constrained or not).
+                //
+                v = 1.0;
+                if( s.nnc[i] && (double)(x[i])==(double)(0.0) )
+                {
+                    v = 0.0;
+                }
+                rotations.generaterotation(v, lambdav, ref cs, ref sn, ref r, _params);
+                trdd[i] = cs*v+sn*lambdav;
+                for(j=0; j<=nd-1; j++)
+                {
+                    v = s.densea[i,j];
+                    trda[i,j] = cs*v;
+                    tmplq[j,i] = -(sn*v);
+                }
+            }
+            for(j=0; j<=nd-1; j++)
+            {
+                for(i=ns; i<=nr-1; i++)
+                {
+                    tmplq[j,i] = s.densea[i,j];
+                }
+            }
+            for(j=0; j<=nd-1; j++)
+            {
+                if( s.nnc[ns+j] && (double)(x[ns+j])==(double)(0.0) )
+                {
+                    
+                    //
+                    // Variable is constrained, entire row is set to zero.
+                    //
+                    for(i=0; i<=nr-1; i++)
+                    {
+                        tmplq[j,i] = 0.0;
+                    }
+                    for(i=0; i<=ns-1; i++)
+                    {
+                        trda[i,j] = 0.0;
+                    }
+                }
+            }
+            for(i=0; i<=nd-1; i++)
+            {
+                for(j=0; j<=nd-1; j++)
+                {
+                    tmplq[j,nr+i] = 0.0;
+                }
+                tmplq[i,nr+i] = lambdav*diag[i];
+            }
+            apserv.rvectorsetlengthatleast(ref tmp0, nr+nd+1, _params);
+            apserv.rvectorsetlengthatleast(ref tmp1, nr+nd+1, _params);
+            apserv.rvectorsetlengthatleast(ref tmp2, nr+nd+1, _params);
+            ortfac.rmatrixlqbasecase(ref tmplq, nd, nr+nd, ref tmp0, ref tmp1, ref tmp2, _params);
+            for(i=0; i<=nd-1; i++)
+            {
+                if( (double)(tmplq[i,i])<(double)(0.0) )
+                {
+                    for(j=i; j<=nd-1; j++)
+                    {
+                        tmplq[j,i] = -tmplq[j,i];
+                    }
+                }
+            }
+            for(i=0; i<=nd-1; i++)
+            {
+                for(j=0; j<=i; j++)
+                {
+                    trda[ns+j,i] = tmplq[i,j];
+                }
+            }
+        }
+
+
+        private static void trdsolve(double[] trdd,
+            double[,] trda,
+            int ns,
+            int nd,
+            double[] d,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            
+            //
+            // Solve U'*y=d first.
+            //
+            // This section includes two parts:
+            // * solve diagonal part of U'
+            // * solve dense part of U'
+            //
+            for(i=0; i<=ns-1; i++)
+            {
+                d[i] = d[i]/trdd[i];
+                v = d[i];
+                for(j=0; j<=nd-1; j++)
+                {
+                    d[ns+j] = d[ns+j]-v*trda[i,j];
+                }
+            }
+            for(i=0; i<=nd-1; i++)
+            {
+                d[ns+i] = d[ns+i]/trda[ns+i,i];
+                v = d[ns+i];
+                for(j=i+1; j<=nd-1; j++)
+                {
+                    d[ns+j] = d[ns+j]-v*trda[ns+i,j];
+                }
+            }
+            
+            //
+            // Solve U*x=y then.
+            //
+            // This section includes two parts:
+            // * solve trailing triangular part of U
+            // * solve combination of diagonal and dense parts of U
+            //
+            for(i=nd-1; i>=0; i--)
+            {
+                v = 0.0;
+                for(j=i+1; j<=nd-1; j++)
+                {
+                    v = v+trda[ns+i,j]*d[ns+j];
+                }
+                d[ns+i] = (d[ns+i]-v)/trda[ns+i,i];
+            }
+            for(i=ns-1; i>=0; i--)
+            {
+                v = 0.0;
+                for(j=0; j<=nd-1; j++)
+                {
+                    v = v+trda[i,j]*d[ns+j];
+                }
+                d[i] = (d[i]-v)/trdd[i];
+            }
+        }
+
+
+        private static void trdfixvariable(double[] trdd,
+            double[,] trda,
+            int ns,
+            int nd,
+            int idx,
+            ref double[] tmp,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double cs = 0;
+            double sn = 0;
+            double r = 0;
+            double v = 0;
+            double vv = 0;
+
+            alglib.ap.assert(ns>=0, "TRDFixVariable: integrity error");
+            alglib.ap.assert(nd>=0, "TRDFixVariable: integrity error");
+            alglib.ap.assert(ns+nd>0, "TRDFixVariable: integrity error");
+            alglib.ap.assert(idx>=0, "TRDFixVariable: integrity error");
+            alglib.ap.assert(idx<ns+nd, "TRDFixVariable: integrity error");
+            apserv.rvectorsetlengthatleast(ref tmp, nd, _params);
+            
+            //
+            // Depending on variable index, two situations are possible
+            //
+            if( idx<ns )
+            {
+                
+                //
+                // We fix variable in the diagonal part of the model. It means
+                // that prior to fixing we have:
+                //
+                //     (     |     )
+                //     (  D  |     )
+                //     (     |     )
+                //     (-----|  A  )
+                //     (     |0    )
+                //     (     |00   )
+                //     (     |000  )
+                //     (     |0000 )
+                //     (     |00000)
+                //
+                // then we replace idx-th column by zeros:
+                //
+                //     (D 0  |     )
+                //     (  0  |     )
+                //     (  0 D|     )
+                //     (-----|  A  )
+                //     (     |     )
+                //     (     |     )
+                //     (     |     )
+                //
+                // and append row with unit element to bottom, in order to
+                // regularize problem
+                //
+                //     (D 0  |     )
+                //     (  0  |     )
+                //     (  0 D|     )
+                //     (-----|  A  )
+                //     (     |     )
+                //     (     |     )
+                //     (     |     )
+                //     (00100|00000) <- appended
+                //
+                // and then we nullify this row by applying rotations:
+                //
+                //     (D 0  |     )
+                //     (  0  |     ) <- first rotation is applied here
+                //     (  0 D|     )
+                //     (-----|  A  ) <- subsequent rotations are applied to this row and rows below
+                //     (     |     )
+                //     (     |     )
+                //     (     |     )
+                //     (  0  |  0  ) <- as result, row becomes zero
+                //
+                // and triangular structure is preserved
+                //
+                if( nd==0 )
+                {
+                    
+                    //
+                    // Quick exit for empty dense part
+                    //
+                    trdd[idx] = 1.0;
+                    return;
+                }
+                for(j=0; j<=nd-1; j++)
+                {
+                    
+                    //
+                    // Apply first rotation
+                    //
+                    tmp[j] = trda[idx,j];
+                    trda[idx,j] = 0.0;
+                }
+                trdd[idx] = 1.0;
+                for(i=0; i<=nd-1; i++)
+                {
+                    if( (double)(tmp[i])!=(double)(0) )
+                    {
+                        
+                        //
+                        // Apply subsequent rotations with bottom triangular part of A
+                        //
+                        rotations.generaterotation(trda[ns+i,i], tmp[i], ref cs, ref sn, ref r, _params);
+                        for(j=i; j<=nd-1; j++)
+                        {
+                            v = trda[ns+i,j];
+                            vv = tmp[j];
+                            trda[ns+i,j] = v*cs+vv*sn;
+                            tmp[j] = vv*cs-v*sn;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // We fix variable in the dense part of the model. It means
+                // that prior to fixing we have:
+                //
+                //     (     |     )
+                //     (  D  |     )
+                //     (     |     )
+                //     (-----|  A  )
+                //     (     |0    )
+                //     (     |00   )
+                //     (     |000  )
+                //     (     |0000 )
+                //     (     |00000)
+                //
+                // then we replace idx-th column by zeros:
+                //
+                //     (     |  0  )
+                //     (  D  |  0  )
+                //     (     |  0  )
+                //     (-----|A 0 A)
+                //     (     |  0  )
+                //     (     |  0  )
+                //     (     |  0  )
+                //
+                // and append row with unit element to bottom, in order to
+                // regularize problem
+                //
+                //     (     |  0  )
+                //     (  D  |  0  )
+                //     (     |  0  )
+                //     (-----|A 0 A)
+                //     (     |  0  )
+                //     (     |  0  )
+                //     (     |  0  )
+                //     (00000|00100) <- appended
+                //
+                // and then we nullify this row by applying rotations:
+                //
+                //     (D 0  |     )
+                //     (  0  |     )
+                //     (  0 D|     )
+                //     (-----|  A  )
+                //     (     |     )
+                //     (     |     ) <- first rotation is applied here
+                //     (     |     ) <- subsequent rotations are applied to rows below
+                //     (  0  |  0  ) <- as result, row becomes zero
+                //
+                // and triangular structure is preserved.
+                //
+                k = idx-ns;
+                for(i=0; i<=ns+nd-1; i++)
+                {
+                    trda[i,k] = 0.0;
+                }
+                for(j=k+1; j<=nd-1; j++)
+                {
+                    
+                    //
+                    // Apply first rotation
+                    //
+                    tmp[j] = trda[idx,j];
+                    trda[idx,j] = 0.0;
+                }
+                trda[idx,k] = 1.0;
+                for(i=k+1; i<=nd-1; i++)
+                {
+                    if( (double)(tmp[i])!=(double)(0) )
+                    {
+                        
+                        //
+                        // Apply subsequent rotations with bottom triangular part of A
+                        //
+                        rotations.generaterotation(trda[ns+i,i], tmp[i], ref cs, ref sn, ref r, _params);
+                        for(j=i; j<=nd-1; j++)
+                        {
+                            v = trda[ns+i,j];
+                            vv = tmp[j];
+                            trda[ns+i,j] = v*cs+vv*sn;
+                            tmp[j] = vv*cs-v*sn;
+                        }
+                    }
+                }
+            }
+        }
+
+
+    }
+    public class sactivesets
+    {
+        /*************************************************************************
+        This structure describes set of linear constraints (boundary  and  general
+        ones) which can be active and inactive. It also has functionality to  work
+        with current point and current  gradient  (determine  active  constraints,
+        move current point, project gradient into  constrained  subspace,  perform
+        constrained preconditioning and so on.
+
+        This  structure  is  intended  to  be  used  by constrained optimizers for
+        management of all constraint-related functionality.
+
+        External code may access following internal fields of the structure:
+            XC          -   stores current point, array[N].
+                            can be accessed only in optimization mode
+            CStatus     -   active set, array[N+NEC+NIC]:
+                            * CStatus[I]>0    I-th constraint is in the active set
+                            * CStatus[I]=0    I-th constraint is at the boundary, but inactive
+                            * CStatus[I]<0    I-th constraint is far from the boundary (and inactive)
+                            * elements from 0 to N-1 correspond to boundary constraints
+                            * elements from N to N+NEC+NIC-1 correspond to linear constraints
+                            * elements from N to N+NEC-1 are always +1
+            FeasInitPt  -   SASStartOptimization() sets this flag to True if, after
+                            enforcement  of  box  constraints,  initial  point was
+                            feasible w.r.t. general linear constraints.
+                            This field can be used by unit test to  validate  some
+                            details of initial point calculation algorithm.
+            SparseBatch -   indices of box constraints which are NOT included into
+                            dense batch (were activated at the beginning of the
+                            orthogonalization); SparseBatchSize elements are stored.
+            SparseBatchSize-size of sparse batcj
+            PDenseBatch,
+            IDenseBatch,
+            SDenseBatch -   after call to SASRebuildBasis() these  matrices  store
+                            active constraints (except for ones included into sparse
+                            batch), reorthogonalized with respect to some inner
+                            product:
+                            a) for "P" batch - one  given  by preconditioner matrix
+                               (inverse Hessian)
+                            b) for "S" one - one given by square of the scale matrix
+                            c) for "I" one - traditional dot product
+                            array[DenseBatchSize,N+1]
+                            All three matrices are linearly equivalent to each
+                            other.
+                            
+                            IMPORTANT: you have to call  SASRebuildBasis()  before
+                                       accessing these arrays  in  order  to  make
+                                       sure that they are up to date.
+            DenseBatchSize   -   dense batch size (PBasis/SBasis/IBasis)
+        *************************************************************************/
+        public class sactiveset : apobject
+        {
+            public int n;
+            public int algostate;
+            public double[] xc;
+            public bool hasxc;
+            public double[] s;
+            public double[] h;
+            public int[] cstatus;
+            public bool basisisready;
+            public double[,] sdensebatch;
+            public double[,] pdensebatch;
+            public double[,] idensebatch;
+            public int densebatchsize;
+            public int[] sparsebatch;
+            public int sparsebatchsize;
+            public int basisage;
+            public bool feasinitpt;
+            public bool constraintschanged;
+            public bool[] hasbndl;
+            public bool[] hasbndu;
+            public double[] bndl;
+            public double[] bndu;
+            public double[,] cleic;
+            public int nec;
+            public int nic;
+            public bool[] mtnew;
+            public double[] mtx;
+            public int[] mtas;
+            public double[] cdtmp;
+            public double[] corrtmp;
+            public double[] unitdiagonal;
+            public snnls.snnlssolver solver;
+            public double[] scntmp;
+            public double[] tmp0;
+            public double[] tmpfeas;
+            public double[,] tmpm0;
+            public double[] rctmps;
+            public double[] rctmpg;
+            public double[] rctmprightpart;
+            public double[,] rctmpdense0;
+            public double[,] rctmpdense1;
+            public bool[] rctmpisequality;
+            public int[] rctmpconstraintidx;
+            public double[] rctmplambdas;
+            public double[,] tmpbasis;
+            public double[] tmpnormestimates;
+            public double[] tmpreciph;
+            public double[] tmpprodp;
+            public double[] tmpprods;
+            public double[] tmpcp;
+            public double[] tmpcs;
+            public double[] tmpci;
+            public sactiveset()
+            {
+                init();
+            }
+            public override void init()
+            {
+                xc = new double[0];
+                s = new double[0];
+                h = new double[0];
+                cstatus = new int[0];
+                sdensebatch = new double[0,0];
+                pdensebatch = new double[0,0];
+                idensebatch = new double[0,0];
+                sparsebatch = new int[0];
+                hasbndl = new bool[0];
+                hasbndu = new bool[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                cleic = new double[0,0];
+                mtnew = new bool[0];
+                mtx = new double[0];
+                mtas = new int[0];
+                cdtmp = new double[0];
+                corrtmp = new double[0];
+                unitdiagonal = new double[0];
+                solver = new snnls.snnlssolver();
+                scntmp = new double[0];
+                tmp0 = new double[0];
+                tmpfeas = new double[0];
+                tmpm0 = new double[0,0];
+                rctmps = new double[0];
+                rctmpg = new double[0];
+                rctmprightpart = new double[0];
+                rctmpdense0 = new double[0,0];
+                rctmpdense1 = new double[0,0];
+                rctmpisequality = new bool[0];
+                rctmpconstraintidx = new int[0];
+                rctmplambdas = new double[0];
+                tmpbasis = new double[0,0];
+                tmpnormestimates = new double[0];
+                tmpreciph = new double[0];
+                tmpprodp = new double[0];
+                tmpprods = new double[0];
+                tmpcp = new double[0];
+                tmpcs = new double[0];
+                tmpci = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                sactiveset _result = new sactiveset();
+                _result.n = n;
+                _result.algostate = algostate;
+                _result.xc = (double[])xc.Clone();
+                _result.hasxc = hasxc;
+                _result.s = (double[])s.Clone();
+                _result.h = (double[])h.Clone();
+                _result.cstatus = (int[])cstatus.Clone();
+                _result.basisisready = basisisready;
+                _result.sdensebatch = (double[,])sdensebatch.Clone();
+                _result.pdensebatch = (double[,])pdensebatch.Clone();
+                _result.idensebatch = (double[,])idensebatch.Clone();
+                _result.densebatchsize = densebatchsize;
+                _result.sparsebatch = (int[])sparsebatch.Clone();
+                _result.sparsebatchsize = sparsebatchsize;
+                _result.basisage = basisage;
+                _result.feasinitpt = feasinitpt;
+                _result.constraintschanged = constraintschanged;
+                _result.hasbndl = (bool[])hasbndl.Clone();
+                _result.hasbndu = (bool[])hasbndu.Clone();
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.cleic = (double[,])cleic.Clone();
+                _result.nec = nec;
+                _result.nic = nic;
+                _result.mtnew = (bool[])mtnew.Clone();
+                _result.mtx = (double[])mtx.Clone();
+                _result.mtas = (int[])mtas.Clone();
+                _result.cdtmp = (double[])cdtmp.Clone();
+                _result.corrtmp = (double[])corrtmp.Clone();
+                _result.unitdiagonal = (double[])unitdiagonal.Clone();
+                _result.solver = (snnls.snnlssolver)solver.make_copy();
+                _result.scntmp = (double[])scntmp.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmpfeas = (double[])tmpfeas.Clone();
+                _result.tmpm0 = (double[,])tmpm0.Clone();
+                _result.rctmps = (double[])rctmps.Clone();
+                _result.rctmpg = (double[])rctmpg.Clone();
+                _result.rctmprightpart = (double[])rctmprightpart.Clone();
+                _result.rctmpdense0 = (double[,])rctmpdense0.Clone();
+                _result.rctmpdense1 = (double[,])rctmpdense1.Clone();
+                _result.rctmpisequality = (bool[])rctmpisequality.Clone();
+                _result.rctmpconstraintidx = (int[])rctmpconstraintidx.Clone();
+                _result.rctmplambdas = (double[])rctmplambdas.Clone();
+                _result.tmpbasis = (double[,])tmpbasis.Clone();
+                _result.tmpnormestimates = (double[])tmpnormestimates.Clone();
+                _result.tmpreciph = (double[])tmpreciph.Clone();
+                _result.tmpprodp = (double[])tmpprodp.Clone();
+                _result.tmpprods = (double[])tmpprods.Clone();
+                _result.tmpcp = (double[])tmpcp.Clone();
+                _result.tmpcs = (double[])tmpcs.Clone();
+                _result.tmpci = (double[])tmpci.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        public const int maxbasisage = 5;
+        public const double maxbasisdecay = 0.01;
+        public const double minnormseparation = 0.25;
+
+
+        /*************************************************************************
+        This   subroutine   is   used  to initialize active set. By default, empty
+        N-variable model with no constraints is  generated.  Previously  allocated
+        buffer variables are reused as much as possible.
+
+        Two use cases for this object are described below.
+
+        CASE 1 - STEEPEST DESCENT:
+
+            SASInit()
+            repeat:
+                SASReactivateConstraints()
+                SASDescentDirection()
+                SASExploreDirection()
+                SASMoveTo()
+            until convergence
+
+        CASE 1 - PRECONDITIONED STEEPEST DESCENT:
+
+            SASInit()
+            repeat:
+                SASReactivateConstraintsPrec()
+                SASDescentDirectionPrec()
+                SASExploreDirection()
+                SASMoveTo()
+            until convergence
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sasinit(int n,
+            sactiveset s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            s.n = n;
+            s.algostate = 0;
+            
+            //
+            // Constraints
+            //
+            s.constraintschanged = true;
+            s.nec = 0;
+            s.nic = 0;
+            apserv.rvectorsetlengthatleast(ref s.bndl, n, _params);
+            apserv.bvectorsetlengthatleast(ref s.hasbndl, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.bndu, n, _params);
+            apserv.bvectorsetlengthatleast(ref s.hasbndu, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                s.bndl[i] = Double.NegativeInfinity;
+                s.bndu[i] = Double.PositiveInfinity;
+                s.hasbndl[i] = false;
+                s.hasbndu[i] = false;
+            }
+            
+            //
+            // current point, scale
+            //
+            s.hasxc = false;
+            apserv.rvectorsetlengthatleast(ref s.xc, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.s, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.h, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                s.xc[i] = 0.0;
+                s.s[i] = 1.0;
+                s.h[i] = 1.0;
+            }
+            
+            //
+            // Other
+            //
+            apserv.rvectorsetlengthatleast(ref s.unitdiagonal, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                s.unitdiagonal[i] = 1.0;
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets scaling coefficients for SAS object.
+
+        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
+
+        During orthogonalization phase, scale is used to calculate drop tolerances
+        (whether vector is significantly non-zero or not).
+
+        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 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sassetscale(sactiveset state,
+            double[] s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(state.algostate==0, "SASSetScale: you may change scale only in modification mode");
+            alglib.ap.assert(alglib.ap.len(s)>=state.n, "SASSetScale: Length(S)<N");
+            for(i=0; i<=state.n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(s[i]), "SASSetScale: S contains infinite or NAN elements");
+                alglib.ap.assert((double)(s[i])!=(double)(0), "SASSetScale: S contains zero elements");
+            }
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.s[i] = Math.Abs(s[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        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 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sassetprecdiag(sactiveset state,
+            double[] d,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(state.algostate==0, "SASSetPrecDiag: you may change preconditioner only in modification mode");
+            alglib.ap.assert(alglib.ap.len(d)>=state.n, "SASSetPrecDiag: D is too short");
+            for(i=0; i<=state.n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(d[i]), "SASSetPrecDiag: D contains infinite or NAN elements");
+                alglib.ap.assert((double)(d[i])>(double)(0), "SASSetPrecDiag: D contains non-positive elements");
+            }
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.h[i] = d[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets/changes boundary constraints.
+
+        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].
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sassetbc(sactiveset state,
+            double[] bndl,
+            double[] bndu,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+
+            alglib.ap.assert(state.algostate==0, "SASSetBC: you may change constraints only in modification mode");
+            n = state.n;
+            alglib.ap.assert(alglib.ap.len(bndl)>=n, "SASSetBC: Length(BndL)<N");
+            alglib.ap.assert(alglib.ap.len(bndu)>=n, "SASSetBC: Length(BndU)<N");
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(bndl[i]) || Double.IsNegativeInfinity(bndl[i]), "SASSetBC: BndL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(bndu[i]) || Double.IsPositiveInfinity(bndu[i]), "SASSetBC: BndL contains NAN or -INF");
+                state.bndl[i] = bndl[i];
+                state.hasbndl[i] = math.isfinite(bndl[i]);
+                state.bndu[i] = bndu[i];
+                state.hasbndu[i] = math.isfinite(bndu[i]);
+            }
+            state.constraintschanged = true;
+        }
+
+
+        /*************************************************************************
+        This function sets linear constraints for SAS object.
+
+        Linear constraints are inactive by default (after initial creation).
+
+        INPUT PARAMETERS:
+            State   -   SAS structure
+            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
+
+        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 sassetlc(sactiveset state,
+            double[,] c,
+            int[] ct,
+            int k,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(state.algostate==0, "SASSetLC: you may change constraints only in modification mode");
+            n = state.n;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(k>=0, "SASSetLC: K<0");
+            alglib.ap.assert(alglib.ap.cols(c)>=n+1 || k==0, "SASSetLC: Cols(C)<N+1");
+            alglib.ap.assert(alglib.ap.rows(c)>=k, "SASSetLC: Rows(C)<K");
+            alglib.ap.assert(alglib.ap.len(ct)>=k, "SASSetLC: Length(CT)<K");
+            alglib.ap.assert(apserv.apservisfinitematrix(c, k, n+1, _params), "SASSetLC: C contains infinite or NaN values!");
+            
+            //
+            // Handle zero K
+            //
+            if( k==0 )
+            {
+                state.nec = 0;
+                state.nic = 0;
+                state.constraintschanged = true;
+                return;
+            }
+            
+            //
+            // Equality constraints are stored first, in the upper
+            // NEC rows of State.CLEIC matrix. Inequality constraints
+            // are stored in the next NIC rows.
+            //
+            // NOTE: we convert inequality constraints to the form
+            // A*x<=b before copying them.
+            //
+            apserv.rmatrixsetlengthatleast(ref state.cleic, k, n+1, _params);
+            state.nec = 0;
+            state.nic = 0;
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]==0 )
+                {
+                    for(i_=0; i_<=n;i_++)
+                    {
+                        state.cleic[state.nec,i_] = c[i,i_];
+                    }
+                    state.nec = state.nec+1;
+                }
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]!=0 )
+                {
+                    if( ct[i]>0 )
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec+state.nic,i_] = -c[i,i_];
+                        }
+                    }
+                    else
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec+state.nic,i_] = c[i,i_];
+                        }
+                    }
+                    state.nic = state.nic+1;
+                }
+            }
+            
+            //
+            // Mark state as changed
+            //
+            state.constraintschanged = true;
+        }
+
+
+        /*************************************************************************
+        Another variation of SASSetLC(), which accepts  linear  constraints  using
+        another representation.
+
+        Linear constraints are inactive by default (after initial creation).
+
+        INPUT PARAMETERS:
+            State   -   SAS structure
+            CLEIC   -   linear constraints, array[NEC+NIC,N+1].
+                        Each row of C represents one constraint:
+                        * first N elements correspond to coefficients,
+                        * last element corresponds to the right part.
+                        First NEC rows store equality constraints, next NIC -  are
+                        inequality ones.
+                        All elements of C (including right part) must be finite.
+            NEC     -   number of equality constraints, NEC>=0
+            NIC     -   number of inequality constraints, NIC>=0
+
+        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 sassetlcx(sactiveset state,
+            double[,] cleic,
+            int nec,
+            int nic,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+
+            alglib.ap.assert(state.algostate==0, "SASSetLCX: you may change constraints only in modification mode");
+            n = state.n;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(nec>=0, "SASSetLCX: NEC<0");
+            alglib.ap.assert(nic>=0, "SASSetLCX: NIC<0");
+            alglib.ap.assert(alglib.ap.cols(cleic)>=n+1 || nec+nic==0, "SASSetLCX: Cols(CLEIC)<N+1");
+            alglib.ap.assert(alglib.ap.rows(cleic)>=nec+nic, "SASSetLCX: Rows(CLEIC)<NEC+NIC");
+            alglib.ap.assert(apserv.apservisfinitematrix(cleic, nec+nic, n+1, _params), "SASSetLCX: CLEIC contains infinite or NaN values!");
+            
+            //
+            // Store constraints
+            //
+            apserv.rmatrixsetlengthatleast(ref state.cleic, nec+nic, n+1, _params);
+            state.nec = nec;
+            state.nic = nic;
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                for(j=0; j<=n; j++)
+                {
+                    state.cleic[i,j] = cleic[i,j];
+                }
+            }
+            
+            //
+            // Mark state as changed
+            //
+            state.constraintschanged = true;
+        }
+
+
+        /*************************************************************************
+        This subroutine turns on optimization mode:
+        1. feasibility in X is enforced  (in case X=S.XC and constraints  have not
+           changed, algorithm just uses X without any modifications at all)
+        2. constraints are marked as "candidate" or "inactive"
+
+        INPUT PARAMETERS:
+            S   -   active set object
+            X   -   initial point (candidate), array[N]. It is expected that X
+                    contains only finite values (we do not check it).
+            
+        OUTPUT PARAMETERS:
+            S   -   state is changed
+            X   -   initial point can be changed to enforce feasibility
+            
+        RESULT:
+            True in case feasible point was found (mode was changed to "optimization")
+            False in case no feasible point was found (mode was not changed)
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool sasstartoptimization(sactiveset state,
+            double[] x,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int nec = 0;
+            int nic = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double vv = 0;
+            double vc = 0;
+            double vx = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(state.algostate==0, "SASStartOptimization: already in optimization mode");
+            result = false;
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            
+            //
+            // Enforce feasibility and calculate set of "candidate"/"active" constraints.
+            // Always active equality constraints are marked as "active", all other constraints
+            // are marked as "candidate".
+            //
+            apserv.ivectorsetlengthatleast(ref state.cstatus, n+nec+nic, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.hasbndl[i] && state.hasbndu[i] )
+                {
+                    if( (double)(state.bndl[i])>(double)(state.bndu[i]) )
+                    {
+                        return result;
+                    }
+                }
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xc[i_] = x[i_];
+            }
+            if( state.nec+state.nic>0 )
+            {
+                
+                //
+                // General linear constraints are present.
+                // Try to use fast code for feasible initial point with modest
+                // memory requirements.
+                //
+                apserv.rvectorsetlengthatleast(ref state.tmp0, n, _params);
+                state.feasinitpt = true;
+                for(i=0; i<=n-1; i++)
+                {
+                    state.tmp0[i] = x[i];
+                    state.cstatus[i] = -1;
+                    if( (state.hasbndl[i] && state.hasbndu[i]) && (double)(state.bndl[i])==(double)(state.bndu[i]) )
+                    {
+                        state.tmp0[i] = state.bndl[i];
+                        state.cstatus[i] = 1;
+                        continue;
+                    }
+                    if( state.hasbndl[i] && (double)(state.tmp0[i])<=(double)(state.bndl[i]) )
+                    {
+                        state.cstatus[i] = 0;
+                        state.tmp0[i] = state.bndl[i];
+                    }
+                    if( state.hasbndu[i] && (double)(state.tmp0[i])>=(double)(state.bndu[i]) )
+                    {
+                        state.cstatus[i] = 0;
+                        state.tmp0[i] = state.bndu[i];
+                    }
+                }
+                for(i=0; i<=state.nec+state.nic-1; i++)
+                {
+                    v = -state.cleic[i,n];
+                    v0 = 0;
+                    v1 = 0;
+                    for(j=0; j<=n-1; j++)
+                    {
+                        vx = state.tmp0[j]/state.s[j];
+                        vc = state.cleic[i,j]*state.s[j];
+                        v = v+vx*vc;
+                        v0 = v0+math.sqr(vx);
+                        v1 = v1+math.sqr(vc);
+                    }
+                    vv = Math.Sqrt(v0)*Math.Sqrt(v1)*1000*math.machineepsilon;
+                    if( i<state.nec )
+                    {
+                        state.cstatus[n+i] = 1;
+                        state.feasinitpt = state.feasinitpt && (double)(Math.Abs(v))<(double)(vv);
+                    }
+                    else
+                    {
+                        state.feasinitpt = state.feasinitpt && (double)(v)<(double)(vv);
+                        if( (double)(v)<(double)(-vv) )
+                        {
+                            state.cstatus[n+i] = -1;
+                        }
+                        else
+                        {
+                            state.cstatus[n+i] = 0;
+                        }
+                    }
+                }
+                if( state.feasinitpt )
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.xc[i_] = state.tmp0[i_];
+                    }
+                }
+                
+                //
+                // Fast code failed? Use general code with ~(N+NIC)^2 memory requirements
+                //
+                if( !state.feasinitpt )
+                {
+                    apserv.rvectorsetlengthatleast(ref state.tmp0, n, _params);
+                    apserv.rvectorsetlengthatleast(ref state.tmpfeas, n+state.nic, _params);
+                    apserv.rmatrixsetlengthatleast(ref state.tmpm0, state.nec+state.nic, n+state.nic+1, _params);
+                    for(i=0; i<=state.nec+state.nic-1; i++)
+                    {
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            state.tmpm0[i,i_] = state.cleic[i,i_];
+                        }
+                        for(j=n; j<=n+state.nic-1; j++)
+                        {
+                            state.tmpm0[i,j] = 0;
+                        }
+                        if( i>=state.nec )
+                        {
+                            state.tmpm0[i,n+i-state.nec] = 1.0;
+                        }
+                        state.tmpm0[i,n+state.nic] = state.cleic[i,n];
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.tmpfeas[i_] = state.xc[i_];
+                    }
+                    for(i=0; i<=state.nic-1; i++)
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            v += state.cleic[i+state.nec,i_]*state.xc[i_];
+                        }
+                        state.tmpfeas[i+n] = Math.Max(state.cleic[i+state.nec,n]-v, 0.0);
+                    }
+                    if( !optserv.findfeasiblepoint(ref state.tmpfeas, state.bndl, state.hasbndl, state.bndu, state.hasbndu, n, state.nic, state.tmpm0, state.nec+state.nic, 1.0E-6, ref i, ref j, _params) )
+                    {
+                        return result;
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.xc[i_] = state.tmpfeas[i_];
+                    }
+                    for(i=0; i<=n-1; i++)
+                    {
+                        if( (state.hasbndl[i] && state.hasbndu[i]) && (double)(state.bndl[i])==(double)(state.bndu[i]) )
+                        {
+                            state.cstatus[i] = 1;
+                            continue;
+                        }
+                        if( (state.hasbndl[i] && (double)(state.xc[i])==(double)(state.bndl[i])) || (state.hasbndu[i] && (double)(state.xc[i])==(double)(state.bndu[i])) )
+                        {
+                            state.cstatus[i] = 0;
+                            continue;
+                        }
+                        state.cstatus[i] = -1;
+                    }
+                    for(i=0; i<=state.nec-1; i++)
+                    {
+                        state.cstatus[n+i] = 1;
+                    }
+                    for(i=0; i<=state.nic-1; i++)
+                    {
+                        if( (double)(state.tmpfeas[n+i])==(double)(0) )
+                        {
+                            state.cstatus[n+state.nec+i] = 0;
+                        }
+                        else
+                        {
+                            state.cstatus[n+state.nec+i] = -1;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // Only box constraints are present, quick code can be used
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    state.cstatus[i] = -1;
+                    if( (state.hasbndl[i] && state.hasbndu[i]) && (double)(state.bndl[i])==(double)(state.bndu[i]) )
+                    {
+                        state.cstatus[i] = 1;
+                        state.xc[i] = state.bndl[i];
+                        continue;
+                    }
+                    if( state.hasbndl[i] && (double)(state.xc[i])<=(double)(state.bndl[i]) )
+                    {
+                        state.xc[i] = state.bndl[i];
+                        state.cstatus[i] = 0;
+                        continue;
+                    }
+                    if( state.hasbndu[i] && (double)(state.xc[i])>=(double)(state.bndu[i]) )
+                    {
+                        state.xc[i] = state.bndu[i];
+                        state.cstatus[i] = 0;
+                        continue;
+                    }
+                }
+                state.feasinitpt = true;
+            }
+            
+            //
+            // Change state, allocate temporaries
+            //
+            result = true;
+            state.algostate = 1;
+            state.basisisready = false;
+            state.hasxc = true;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function explores search direction and calculates bound for  step  as
+        well as information for activation of constraints.
+
+        INPUT PARAMETERS:
+            State       -   SAS structure which stores current point and all other
+                            active set related information
+            D           -   descent direction to explore
+
+        OUTPUT PARAMETERS:
+            StpMax      -   upper  limit  on  step  length imposed by yet inactive
+                            constraints. Can be  zero  in  case  some  constraints
+                            can be activated by zero step.  Equal  to  some  large
+                            value in case step is unlimited.
+            CIdx        -   -1 for unlimited step, in [0,N+NEC+NIC) in case of
+                            limited step.
+            VVal        -   value which is assigned to X[CIdx] during activation.
+                            For CIdx<0 or CIdx>=N some dummy value is assigned to
+                            this parameter.
+        *************************************************************************/
+        public static void sasexploredirection(sactiveset state,
+            double[] d,
+            ref double stpmax,
+            ref int cidx,
+            ref double vval,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int nec = 0;
+            int nic = 0;
+            int i = 0;
+            double prevmax = 0;
+            double vc = 0;
+            double vd = 0;
+            int i_ = 0;
+
+            stpmax = 0;
+            cidx = 0;
+            vval = 0;
+
+            alglib.ap.assert(state.algostate==1, "SASExploreDirection: is not in optimization mode");
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            cidx = -1;
+            vval = 0;
+            stpmax = 1.0E50;
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.cstatus[i]<=0 )
+                {
+                    alglib.ap.assert(!state.hasbndl[i] || (double)(state.xc[i])>=(double)(state.bndl[i]), "SASExploreDirection: internal error - infeasible X");
+                    alglib.ap.assert(!state.hasbndu[i] || (double)(state.xc[i])<=(double)(state.bndu[i]), "SASExploreDirection: internal error - infeasible X");
+                    if( state.hasbndl[i] && (double)(d[i])<(double)(0) )
+                    {
+                        prevmax = stpmax;
+                        stpmax = apserv.safeminposrv(state.xc[i]-state.bndl[i], -d[i], stpmax, _params);
+                        if( (double)(stpmax)<(double)(prevmax) )
+                        {
+                            cidx = i;
+                            vval = state.bndl[i];
+                        }
+                    }
+                    if( state.hasbndu[i] && (double)(d[i])>(double)(0) )
+                    {
+                        prevmax = stpmax;
+                        stpmax = apserv.safeminposrv(state.bndu[i]-state.xc[i], d[i], stpmax, _params);
+                        if( (double)(stpmax)<(double)(prevmax) )
+                        {
+                            cidx = i;
+                            vval = state.bndu[i];
+                        }
+                    }
+                }
+            }
+            for(i=nec; i<=nec+nic-1; i++)
+            {
+                if( state.cstatus[n+i]<=0 )
+                {
+                    vc = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        vc += state.cleic[i,i_]*state.xc[i_];
+                    }
+                    vc = vc-state.cleic[i,n];
+                    vd = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        vd += state.cleic[i,i_]*d[i_];
+                    }
+                    if( (double)(vd)<=(double)(0) )
+                    {
+                        continue;
+                    }
+                    if( (double)(vc)<(double)(0) )
+                    {
+                        
+                        //
+                        // XC is strictly feasible with respect to I-th constraint,
+                        // we can perform non-zero step because there is non-zero distance
+                        // between XC and bound.
+                        //
+                        prevmax = stpmax;
+                        stpmax = apserv.safeminposrv(-vc, vd, stpmax, _params);
+                        if( (double)(stpmax)<(double)(prevmax) )
+                        {
+                            cidx = n+i;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // XC is at the boundary (or slightly beyond it), and step vector
+                        // points beyond the boundary.
+                        //
+                        // The only thing we can do is to perform zero step and activate
+                        // I-th constraint.
+                        //
+                        stpmax = 0;
+                        cidx = n+i;
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine moves current point to XN, which can be:
+        a) point in the direction previously explored  with  SASExploreDirection()
+           function (in this case NeedAct/CIdx/CVal are used)
+        b) point in arbitrary direction, not necessarily previously  checked  with
+           SASExploreDirection() function.
+
+        Step may activate one constraint. It is assumed than XN  is  approximately
+        feasible (small error as  large  as several  ulps  is  possible).   Strict
+        feasibility  with  respect  to  bound  constraints  is  enforced    during
+        activation, feasibility with respect to general linear constraints is  not
+        enforced.
+
+        This function activates boundary constraints, such that both is True:
+        1) XC[I] is not at the boundary
+        2) XN[I] is at the boundary or beyond it
+
+        INPUT PARAMETERS:
+            S       -   active set object
+            XN      -   new point.
+            NeedAct -   True in case one constraint needs activation
+            CIdx    -   index of constraint, in [0,N+NEC+NIC).
+                        Ignored if NeedAct is false.
+                        This value is calculated by SASExploreDirection().
+            CVal    -   for CIdx in [0,N) this field stores value which is
+                        assigned to XC[CIdx] during activation. CVal is ignored in
+                        other cases.
+                        This value is calculated by SASExploreDirection().
+            
+        OUTPUT PARAMETERS:
+            S       -   current point and list of active constraints are changed.
+
+        RESULT:
+            >0, in case at least one inactive non-candidate constraint was activated
+            =0, in case only "candidate" constraints were activated
+            <0, in case no constraints were activated by the step
+
+        NOTE: in general case State.XC<>XN because activation of  constraints  may
+              slightly change current point (to enforce feasibility).
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static int sasmoveto(sactiveset state,
+            double[] xn,
+            bool needact,
+            int cidx,
+            double cval,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int n = 0;
+            int nec = 0;
+            int nic = 0;
+            int i = 0;
+            bool wasactivation = new bool();
+
+            alglib.ap.assert(state.algostate==1, "SASMoveTo: is not in optimization mode");
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            
+            //
+            // Save previous state, update current point
+            //
+            apserv.rvectorsetlengthatleast(ref state.mtx, n, _params);
+            apserv.ivectorsetlengthatleast(ref state.mtas, n+nec+nic, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.mtx[i] = state.xc[i];
+                state.xc[i] = xn[i];
+            }
+            for(i=0; i<=n+nec+nic-1; i++)
+            {
+                state.mtas[i] = state.cstatus[i];
+            }
+            
+            //
+            // Activate constraints
+            //
+            apserv.bvectorsetlengthatleast(ref state.mtnew, n+nec+nic, _params);
+            wasactivation = false;
+            for(i=0; i<=n+nec+nic-1; i++)
+            {
+                state.mtnew[i] = false;
+            }
+            if( needact )
+            {
+                
+                //
+                // Activation
+                //
+                alglib.ap.assert(cidx>=0 && cidx<n+nec+nic, "SASMoveTo: incorrect CIdx");
+                if( cidx<n )
+                {
+                    
+                    //
+                    // CIdx in [0,N-1] means that bound constraint was activated.
+                    // We activate it explicitly to avoid situation when roundoff-error
+                    // prevents us from moving EXACTLY to x=CVal.
+                    //
+                    state.xc[cidx] = cval;
+                }
+                state.cstatus[cidx] = 1;
+                state.mtnew[cidx] = true;
+                wasactivation = true;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Post-check (some constraints may be activated because of numerical errors)
+                //
+                if( (state.hasbndl[i] && (double)(state.xc[i])<=(double)(state.bndl[i])) && (double)(state.xc[i])!=(double)(state.mtx[i]) )
+                {
+                    state.xc[i] = state.bndl[i];
+                    state.cstatus[i] = 1;
+                    state.mtnew[i] = true;
+                    wasactivation = true;
+                }
+                if( (state.hasbndu[i] && (double)(state.xc[i])>=(double)(state.bndu[i])) && (double)(state.xc[i])!=(double)(state.mtx[i]) )
+                {
+                    state.xc[i] = state.bndu[i];
+                    state.cstatus[i] = 1;
+                    state.mtnew[i] = true;
+                    wasactivation = true;
+                }
+            }
+            
+            //
+            // Determine return status:
+            // * -1 in case no constraints were activated
+            // *  0 in case only "candidate" constraints were activated
+            // * +1 in case at least one "non-candidate" constraint was activated
+            //
+            if( wasactivation )
+            {
+                
+                //
+                // Step activated one/several constraints, but sometimes it is spurious
+                // activation - RecalculateConstraints() tells us that constraint is
+                // inactive (negative Largrange multiplier), but step activates it
+                // because of numerical noise.
+                //
+                // This block of code checks whether step activated truly new constraints
+                // (ones which were not in the active set at the solution):
+                //
+                // * for non-boundary constraint it is enough to check that previous value
+                //   of CStatus[i] is negative (=far from boundary), and new one is
+                //   positive (=we are at the boundary, constraint is activated).
+                //
+                // * for boundary constraints previous criterion won't work. Each variable
+                //   has two constraints, and simply checking their status is not enough -
+                //   we have to correctly identify cases when we leave one boundary
+                //   (PrevActiveSet[i]=0) and move to another boundary (CStatus[i]>0).
+                //   Such cases can be identified if we compare previous X with new X.
+                //
+                // In case only "candidate" constraints were activated, result variable
+                // is set to 0. In case at least one new constraint was activated, result
+                // is set to 1.
+                //
+                result = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( state.cstatus[i]>0 && (double)(state.xc[i])!=(double)(state.mtx[i]) )
+                    {
+                        result = 1;
+                    }
+                }
+                for(i=n; i<=n+state.nec+state.nic-1; i++)
+                {
+                    if( state.mtas[i]<0 && state.cstatus[i]>0 )
+                    {
+                        result = 1;
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // No activation, return -1
+                //
+                result = -1;
+            }
+            
+            //
+            // Update basis
+            //
+            sasappendtobasis(state, state.mtnew, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine performs immediate activation of one constraint:
+        * "immediate" means that we do not have to move to activate it
+        * in case boundary constraint is activated, we enforce current point to be
+          exactly at the boundary
+
+        INPUT PARAMETERS:
+            S       -   active set object
+            CIdx    -   index of constraint, in [0,N+NEC+NIC).
+                        This value is calculated by SASExploreDirection().
+            CVal    -   for CIdx in [0,N) this field stores value which is
+                        assigned to XC[CIdx] during activation. CVal is ignored in
+                        other cases.
+                        This value is calculated by SASExploreDirection().
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sasimmediateactivation(sactiveset state,
+            int cidx,
+            double cval,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(state.algostate==1, "SASMoveTo: is not in optimization mode");
+            if( cidx<state.n )
+            {
+                state.xc[cidx] = cval;
+            }
+            state.cstatus[cidx] = 1;
+            apserv.bvectorsetlengthatleast(ref state.mtnew, state.n+state.nec+state.nic, _params);
+            for(i=0; i<=state.n+state.nec+state.nic-1; i++)
+            {
+                state.mtnew[i] = false;
+            }
+            state.mtnew[cidx] = true;
+            sasappendtobasis(state, state.mtnew, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates descent direction subject to current active set.
+
+        INPUT PARAMETERS:
+            S       -   active set object
+            G       -   array[N], gradient
+            D       -   possibly prealocated buffer;
+                        automatically resized if needed.
+            
+        OUTPUT PARAMETERS:
+            D       -   descent direction projected onto current active set.
+                        Components of D which correspond to active boundary
+                        constraints are forced to be exactly zero.
+                        In case D is non-zero, it is normalized to have unit norm.
+                        
+        NOTE: in  case active set has N  active  constraints  (or  more),  descent
+              direction is forced to be exactly zero.
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sasconstraineddescent(sactiveset state,
+            double[] g,
+            ref double[] d,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(state.algostate==1, "SASConstrainedDescent: is not in optimization mode");
+            sasrebuildbasis(state, _params);
+            constraineddescent(state, g, state.unitdiagonal, state.idensebatch, true, ref d, _params);
+        }
+
+
+        /*************************************************************************
+        This  subroutine  calculates  preconditioned  descent direction subject to
+        current active set.
+
+        INPUT PARAMETERS:
+            S       -   active set object
+            G       -   array[N], gradient
+            D       -   possibly prealocated buffer;
+                        automatically resized if needed.
+            
+        OUTPUT PARAMETERS:
+            D       -   descent direction projected onto current active set.
+                        Components of D which correspond to active boundary
+                        constraints are forced to be exactly zero.
+                        In case D is non-zero, it is normalized to have unit norm.
+                        
+        NOTE: in  case active set has N  active  constraints  (or  more),  descent
+              direction is forced to be exactly zero.
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sasconstraineddescentprec(sactiveset state,
+            double[] g,
+            ref double[] d,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(state.algostate==1, "SASConstrainedDescentPrec: is not in optimization mode");
+            sasrebuildbasis(state, _params);
+            constraineddescent(state, g, state.h, state.pdensebatch, true, ref d, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates projection   of  direction  vector  to  current
+        active set.
+
+        INPUT PARAMETERS:
+            S       -   active set object
+            D       -   array[N], direction
+            
+        OUTPUT PARAMETERS:
+            D       -   direction projected onto current active set.
+                        Components of D which correspond to active boundary
+                        constraints are forced to be exactly zero.
+                        
+        NOTE: in  case active set has N  active  constraints  (or  more),  descent
+              direction is forced to be exactly zero.
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sasconstraineddirection(sactiveset state,
+            ref double[] d,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(state.algostate==1, "SASConstrainedAntigradientPrec: is not in optimization mode");
+            sasrebuildbasis(state, _params);
+            constraineddescent(state, d, state.unitdiagonal, state.idensebatch, false, ref state.cdtmp, _params);
+            for(i=0; i<=state.n-1; i++)
+            {
+                d[i] = -state.cdtmp[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates product of direction vector and  preconditioner
+        multiplied subject to current active set.
+
+        INPUT PARAMETERS:
+            S       -   active set object
+            D       -   array[N], direction
+            
+        OUTPUT PARAMETERS:
+            D       -   preconditioned direction projected onto current active set.
+                        Components of D which correspond to active boundary
+                        constraints are forced to be exactly zero.
+                        
+        NOTE: in  case active set has N  active  constraints  (or  more),  descent
+              direction is forced to be exactly zero.
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sasconstraineddirectionprec(sactiveset state,
+            ref double[] d,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(state.algostate==1, "SASConstrainedAntigradientPrec: is not in optimization mode");
+            sasrebuildbasis(state, _params);
+            constraineddescent(state, d, state.h, state.pdensebatch, false, ref state.cdtmp, _params);
+            for(i=0; i<=state.n-1; i++)
+            {
+                d[i] = -state.cdtmp[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This  subroutine  performs  correction of some (possibly infeasible) point
+        with respect to a) current active set, b) all boundary  constraints,  both
+        active and inactive:
+
+        0) we calculate L1 penalty term for violation of active linear constraints
+           (one which is returned by SASActiveLCPenalty1() function).
+        1) first, it performs projection (orthogonal with respect to scale  matrix
+           S) of X into current active set: X -> X1.
+        2) next, we perform projection with respect to  ALL  boundary  constraints
+           which are violated at X1: X1 -> X2.
+        3) X is replaced by X2.
+
+        The idea is that this function can preserve and enforce feasibility during
+        optimization, and additional penalty parameter can be used to prevent algo
+        from leaving feasible set because of rounding errors.
+
+        INPUT PARAMETERS:
+            S       -   active set object
+            X       -   array[N], candidate point
+            
+        OUTPUT PARAMETERS:
+            X       -   "improved" candidate point:
+                        a) feasible with respect to all boundary constraints
+                        b) feasibility with respect to active set is retained at
+                           good level.
+            Penalty -   penalty term, which can be added to function value if user
+                        wants to penalize violation of constraints (recommended).
+                        
+        NOTE: this function is not intended to find exact  projection  (i.e.  best
+              approximation) of X into feasible set. It just improves situation  a
+              bit.
+              Regular  use  of   this function will help you to retain feasibility
+              - if you already have something to start  with  and  constrain  your
+              steps is such way that the only source of infeasibility are roundoff
+              errors.
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sascorrection(sactiveset state,
+            double[] x,
+            ref double penalty,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+            double v = 0;
+            int i_ = 0;
+
+            penalty = 0;
+
+            alglib.ap.assert(state.algostate==1, "SASCorrection: is not in optimization mode");
+            sasrebuildbasis(state, _params);
+            n = state.n;
+            apserv.rvectorsetlengthatleast(ref state.corrtmp, n, _params);
+            
+            //
+            // Calculate penalty term.
+            //
+            penalty = sasactivelcpenalty1(state, x, _params);
+            
+            //
+            // Perform projection 1.
+            //
+            // This projecton is given by:
+            //
+            //     x_proj = x - S*S*As'*(As*x-b)
+            //
+            // where x is original x before projection, S is a scale matrix,
+            // As is a matrix of equality constraints (active set) which were
+            // orthogonalized with respect to inner product given by S (i.e. we
+            // have As*S*S'*As'=I), b is a right part of the orthogonalized
+            // constraints.
+            //
+            // NOTE: you can verify that x_proj is strictly feasible w.r.t.
+            //       active set by multiplying it by As - you will get
+            //       As*x_proj = As*x - As*x + b = b.
+            //
+            //       This formula for projection can be obtained by solving
+            //       following minimization problem.
+            //
+            //           min ||inv(S)*(x_proj-x)||^2 s.t. As*x_proj=b
+            //
+            // NOTE: we apply sparse batch by examining CStatus[]; it is guaranteed
+            //       to contain sparse batch, but avoids roundoff errors associated
+            //       with the fact that some box constraints were moved to sparse
+            //       storage
+            //       
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.corrtmp[i_] = x[i_];
+            }
+            for(i=0; i<=state.densebatchsize-1; i++)
+            {
+                v = -state.sdensebatch[i,n];
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+state.sdensebatch[i,j]*state.corrtmp[j];
+                }
+                for(j=0; j<=n-1; j++)
+                {
+                    state.corrtmp[j] = state.corrtmp[j]-v*state.sdensebatch[i,j]*math.sqr(state.s[j]);
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.cstatus[i]>0 )
+                {
+                    state.corrtmp[i] = state.xc[i];
+                }
+            }
+            
+            //
+            // Perform projection 2
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                x[i] = state.corrtmp[i];
+                if( state.hasbndl[i] && (double)(x[i])<(double)(state.bndl[i]) )
+                {
+                    x[i] = state.bndl[i];
+                }
+                if( state.hasbndu[i] && (double)(x[i])>(double)(state.bndu[i]) )
+                {
+                    x[i] = state.bndu[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This  subroutine returns L1 penalty for violation of active general linear
+        constraints (violation of boundary or inactive linear constraints  is  not
+        added to penalty).
+
+        Penalty term is equal to:
+            
+            Penalty = SUM( Abs((C_i*x-R_i)/Alpha_i) )
+            
+        Here:
+        * summation is performed for I=0...NEC+NIC-1, CStatus[N+I]>0
+          (only for rows of CLEIC which are in active set)
+        * C_i is I-th row of CLEIC
+        * R_i is corresponding right part
+        * S is a scale matrix
+        * Alpha_i = ||S*C_i|| - is a scaling coefficient which "normalizes"
+          I-th summation term according to its scale.
+
+        INPUT PARAMETERS:
+            S       -   active set object
+            X       -   array[N], candidate point
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double sasactivelcpenalty1(sactiveset state,
+            double[] x,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            int n = 0;
+            int nec = 0;
+            int nic = 0;
+            double v = 0;
+            double alpha = 0;
+            double p = 0;
+
+            alglib.ap.assert(state.algostate==1, "SASActiveLCPenalty1: is not in optimization mode");
+            sasrebuildbasis(state, _params);
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            
+            //
+            // Calculate penalty term.
+            //
+            result = 0;
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                if( state.cstatus[n+i]>0 )
+                {
+                    alpha = 0;
+                    p = -state.cleic[i,n];
+                    for(j=0; j<=n-1; j++)
+                    {
+                        v = state.cleic[i,j];
+                        p = p+v*x[j];
+                        alpha = alpha+math.sqr(v*state.s[j]);
+                    }
+                    alpha = Math.Sqrt(alpha);
+                    if( (double)(alpha)!=(double)(0) )
+                    {
+                        result = result+Math.Abs(p/alpha);
+                    }
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine calculates scaled norm of  vector  after  projection  onto
+        subspace of active constraints. Most often this function is used  to  test
+        stopping conditions.
+
+        INPUT PARAMETERS:
+            S       -   active set object
+            D       -   vector whose norm is calculated
+            
+        RESULT:
+            Vector norm (after projection and scaling)
+            
+        NOTE: projection is performed first, scaling is performed after projection
+                        
+        NOTE: if we have N active constraints, zero value (exact zero) is returned
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static double sasscaledconstrainednorm(sactiveset state,
+            double[] d,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int i = 0;
+            int n = 0;
+            double v = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(state.algostate==1, "SASMoveTo: is not in optimization mode");
+            n = state.n;
+            apserv.rvectorsetlengthatleast(ref state.scntmp, n, _params);
+            
+            //
+            // Prepare basis (if needed)
+            //
+            sasrebuildbasis(state, _params);
+            
+            //
+            // Calculate descent direction
+            //
+            if( state.sparsebatchsize+state.densebatchsize>=n )
+            {
+                
+                //
+                // Quick exit if number of active constraints is N or larger
+                //
+                result = 0.0;
+                return result;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.scntmp[i] = d[i];
+            }
+            for(i=0; i<=state.densebatchsize-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.idensebatch[i,i_]*state.scntmp[i_];
+                }
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.scntmp[i_] = state.scntmp[i_] - v*state.idensebatch[i,i_];
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.cstatus[i]>0 )
+                {
+                    state.scntmp[i] = 0;
+                }
+            }
+            v = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.s[i]*state.scntmp[i]);
+            }
+            result = Math.Sqrt(v);
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine turns off optimization mode.
+
+        INPUT PARAMETERS:
+            S   -   active set object
+            
+        OUTPUT PARAMETERS:
+            S   -   state is changed
+
+        NOTE: this function can be called many times for optimizer which was
+              already stopped.
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sasstopoptimization(sactiveset state,
+            alglib.xparams _params)
+        {
+            state.algostate = 0;
+        }
+
+
+        /*************************************************************************
+        This function recalculates constraints - activates  and  deactivates  them
+        according to gradient value at current point. Algorithm  assumes  that  we
+        want to make steepest descent step from  current  point;  constraints  are
+        activated and deactivated in such way that we won't violate any constraint
+        by steepest descent step.
+
+        After call to this function active set is ready to  try  steepest  descent
+        step (SASDescentDirection-SASExploreDirection-SASMoveTo).
+
+        Only already "active" and "candidate" elements of ActiveSet are  examined;
+        constraints which are not active are not examined.
+
+        INPUT PARAMETERS:
+            State       -   active set object
+            GC          -   array[N], gradient at XC
+            
+        OUTPUT PARAMETERS:
+            State       -   active set object, with new set of constraint
+
+          -- ALGLIB --
+             Copyright 26.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sasreactivateconstraints(sactiveset state,
+            double[] gc,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(state.algostate==1, "SASReactivateConstraints: must be in optimization mode");
+            reactivateconstraints(state, gc, state.unitdiagonal, _params);
+        }
+
+
+        /*************************************************************************
+        This function recalculates constraints - activates  and  deactivates  them
+        according to gradient value at current point.
+
+        Algorithm  assumes  that  we  want  to make Quasi-Newton step from current
+        point with diagonal Quasi-Newton matrix H. Constraints are  activated  and
+        deactivated in such way that we won't violate any constraint by step.
+
+        After call to  this  function  active set is ready to  try  preconditioned
+        steepest descent step (SASDescentDirection-SASExploreDirection-SASMoveTo).
+
+        Only already "active" and "candidate" elements of ActiveSet are  examined;
+        constraints which are not active are not examined.
+
+        INPUT PARAMETERS:
+            State       -   active set object
+            GC          -   array[N], gradient at XC
+            
+        OUTPUT PARAMETERS:
+            State       -   active set object, with new set of constraint
+
+          -- ALGLIB --
+             Copyright 26.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sasreactivateconstraintsprec(sactiveset state,
+            double[] gc,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(state.algostate==1, "SASReactivateConstraintsPrec: must be in optimization mode");
+            reactivateconstraints(state, gc, state.h, _params);
+        }
+
+
+        /*************************************************************************
+        This function builds three orthonormal basises for current active set:
+        * P-orthogonal one, which is orthogonalized with inner product
+          (x,y) = x'*P*y, where P=inv(H) is current preconditioner
+        * S-orthogonal one, which is orthogonalized with inner product
+          (x,y) = x'*S'*S*y, where S is diagonal scaling matrix
+        * I-orthogonal one, which is orthogonalized with standard dot product
+
+        NOTE: all sets of orthogonal vectors are guaranteed  to  have  same  size.
+              P-orthogonal basis is built first, I/S-orthogonal basises are forced
+              to have same number of vectors as P-orthogonal one (padded  by  zero
+              vectors if needed).
+              
+        NOTE: this function tracks changes in active set; first call  will  result
+              in reorthogonalization
+
+        INPUT PARAMETERS:
+            State   -   active set object
+            H       -   diagonal preconditioner, H[i]>0
+
+        OUTPUT PARAMETERS:
+            State   -   active set object with new basis
+            
+          -- ALGLIB --
+             Copyright 20.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sasrebuildbasis(sactiveset state,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int nec = 0;
+            int nic = 0;
+            int i = 0;
+            int j = 0;
+            bool hasactivelin = new bool();
+            int candidatescnt = 0;
+            double v = 0;
+            double vv = 0;
+            double vmax = 0;
+            int kmax = 0;
+            int i_ = 0;
+
+            if( state.basisisready )
+            {
+                return;
+            }
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            apserv.rvectorsetlengthatleast(ref state.tmp0, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmpprodp, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmpprods, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmpcp, n+1, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmpcs, n+1, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmpci, n+1, _params);
+            apserv.rmatrixsetlengthatleast(ref state.tmpbasis, nec+nic, n+1, _params);
+            apserv.rmatrixsetlengthatleast(ref state.pdensebatch, nec+nic, n+1, _params);
+            apserv.rmatrixsetlengthatleast(ref state.idensebatch, nec+nic, n+1, _params);
+            apserv.rmatrixsetlengthatleast(ref state.sdensebatch, nec+nic, n+1, _params);
+            apserv.ivectorsetlengthatleast(ref state.sparsebatch, n, _params);
+            state.sparsebatchsize = 0;
+            state.densebatchsize = 0;
+            state.basisage = 0;
+            state.basisisready = true;
+            
+            //
+            // Determine number of active boundary and non-boundary
+            // constraints, move them to TmpBasis. Quick exit if no
+            // non-boundary constraints were detected.
+            //
+            hasactivelin = false;
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                if( state.cstatus[n+i]>0 )
+                {
+                    hasactivelin = true;
+                }
+            }
+            for(j=0; j<=n-1; j++)
+            {
+                if( state.cstatus[j]>0 )
+                {
+                    state.sparsebatch[state.sparsebatchsize] = j;
+                    state.sparsebatchsize = state.sparsebatchsize+1;
+                }
+            }
+            if( !hasactivelin )
+            {
+                return;
+            }
+            
+            //
+            // Prepare precomputed values
+            //
+            apserv.rvectorsetlengthatleast(ref state.tmpreciph, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.tmpreciph[i] = 1/state.h[i];
+            }
+            
+            //
+            // Prepare initial candidate set:
+            // * select active constraints
+            // * normalize (inner product is given by preconditioner)
+            // * orthogonalize with respect to active box constraints
+            // * copy normalized/orthogonalized candidates to PBasis/SBasis/IBasis
+            //
+            candidatescnt = 0;
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                if( state.cstatus[n+i]>0 )
+                {
+                    for(i_=0; i_<=n;i_++)
+                    {
+                        state.tmpbasis[candidatescnt,i_] = state.cleic[i,i_];
+                    }
+                    apserv.inc(ref candidatescnt, _params);
+                }
+            }
+            for(i=0; i<=candidatescnt-1; i++)
+            {
+                v = 0.0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+math.sqr(state.tmpbasis[i,j])*state.tmpreciph[j];
+                }
+                if( (double)(v)>(double)(0) )
+                {
+                    v = 1/Math.Sqrt(v);
+                    for(j=0; j<=n; j++)
+                    {
+                        state.tmpbasis[i,j] = state.tmpbasis[i,j]*v;
+                    }
+                }
+            }
+            for(j=0; j<=n-1; j++)
+            {
+                if( state.cstatus[j]>0 )
+                {
+                    for(i=0; i<=candidatescnt-1; i++)
+                    {
+                        state.tmpbasis[i,n] = state.tmpbasis[i,n]-state.tmpbasis[i,j]*state.xc[j];
+                        state.tmpbasis[i,j] = 0.0;
+                    }
+                }
+            }
+            for(i=0; i<=candidatescnt-1; i++)
+            {
+                for(j=0; j<=n; j++)
+                {
+                    state.pdensebatch[i,j] = state.tmpbasis[i,j];
+                    state.sdensebatch[i,j] = state.tmpbasis[i,j];
+                    state.idensebatch[i,j] = state.tmpbasis[i,j];
+                }
+            }
+            
+            //
+            // Perform orthogonalization of general linear constraints with respect
+            // to each other (constraints in P/S/IBasis are already normalized w.r.t.
+            // box constraints). During this process we select strictly active constraints
+            // from the candidate set, and drop ones which were detected as redundant
+            // during orthogonalization.
+            //
+            // Orthogonalization is performed with the help of Gram-Schmidt process.
+            // Due to accumulation of round-off errors it is beneficial to perform
+            // pivoting, i.e. to select candidate vector with largest norm at each
+            // step.
+            //
+            // First (basic) version of the algorithm is:
+            //     0. split all constraints into two sets: basis ones (initially empty)
+            //        and candidate ones (all constraints)
+            //     1. fill PBasis with H-normalized candidate constraints, fill
+            //        corresponding entries of S/IBasis with corresponding
+            //        (non-normalized) constraints
+            //     2. select row of PBasis with largest norm, move it (and its S/IBasis
+            //        counterparts) to the beginning of the candidate set, H-normalize
+            //        this row (rows of S/IBasis are normalized using corresponding norms).
+            //        Stop if largest row is nearly (or exactly) zero.
+            //     3. orthogonalize remaining rows of P/S/IBasis with respect to
+            //        one chosen at step (2). It can be done efficiently using
+            //        combination of DGEMV/DGER BLAS calls.
+            //     4. increase basis size by one, decrease candidate set size by one,
+            //        goto (2)
+            //
+            // However, naive implementation of the algorithm above spends significant
+            // amount of time in step (2) - selection of row with largest H-norm. Step
+            // (3) can be efficiently implemented with optimized BLAS, but we have no
+            // optimized BLAS kernels for step(2). And because step (3) changes row norms,
+            // step (2) have to be re-calculated every time, which is quite slow.
+            //
+            // We can save significant amount of calculations by noticing that:
+            // * step (3) DECREASES row norms, but never increases it
+            // * we can maintain upper bounds for row H-norms is a separate array,
+            //   use them for initial evaluation of best candidates, and update them
+            //   after we find some promising row (all bounds are invalidated after
+            //   step 3, but their old values still carry some information)
+            // * it is beneficial re-evaluate bounds only for rows which are
+            //   significantly (at least few percents) larger than best one found so far
+            // * because rows are initially normalized, initial values for upper bounds
+            //   can be set to 1.0
+            //
+            alglib.ap.assert(state.densebatchsize==0, "SAS: integrity check failed");
+            alglib.ap.assert((double)(minnormseparation)>(double)(0), "SAS: integrity check failed");
+            apserv.rvectorsetlengthatleast(ref state.tmpnormestimates, candidatescnt, _params);
+            for(i=0; i<=candidatescnt-1; i++)
+            {
+                state.tmpnormestimates[i] = 1.0;
+            }
+            while( state.sparsebatchsize+state.densebatchsize<n )
+            {
+                
+                //
+                // No candidates? We are done!
+                //
+                if( candidatescnt==0 )
+                {
+                    break;
+                }
+                
+                //
+                // Find largest vector
+                //
+                vmax = 0;
+                kmax = -1;
+                for(i=state.densebatchsize; i<=state.densebatchsize+candidatescnt-1; i++)
+                {
+                    
+                    //
+                    // Use upper bound for row norm for initial evaluation.
+                    // Skip rows whose upper bound is less than best candidate
+                    // found so far.
+                    //
+                    // NOTE: in fact, we may skip rows whose upper bound is
+                    //       marginally higher than that of best candidate.
+                    //       No need to perform costly re-evaluation in order
+                    //       to get just few percents of improvement.
+                    //
+                    if( (double)(state.tmpnormestimates[i])<(double)(vmax*(1+minnormseparation)) )
+                    {
+                        continue;
+                    }
+                    
+                    //
+                    // OK, upper bound is large enough... lets perform full
+                    // re-evaluation and update of the estimate.
+                    //
+                    v = 0.0;
+                    for(j=0; j<=n-1; j++)
+                    {
+                        vv = state.pdensebatch[i,j];
+                        v = v+vv*vv*state.tmpreciph[j];
+                    }
+                    v = Math.Sqrt(v);
+                    state.tmpnormestimates[i] = v;
+                    
+                    //
+                    // Now compare with best candidate so far
+                    //
+                    if( (double)(v)>(double)(vmax) )
+                    {
+                        vmax = v;
+                        kmax = i;
+                    }
+                }
+                if( (double)(vmax)<(double)(1.0E4*math.machineepsilon) || kmax<0 )
+                {
+                    
+                    //
+                    // All candidates are either zero or too small (after orthogonalization)
+                    //
+                    candidatescnt = 0;
+                    break;
+                }
+                
+                //
+                // Candidate is selected for inclusion into basis set.
+                //
+                // Move candidate row to the beginning of candidate array (which is
+                // right past the end of the approved basis). Normalize (for P-basis
+                // we perform preconditioner-based normalization, for S-basis - scale
+                // based, for I-basis - identity based).
+                //
+                apserv.swaprows(state.pdensebatch, state.densebatchsize, kmax, n+1, _params);
+                apserv.swaprows(state.sdensebatch, state.densebatchsize, kmax, n+1, _params);
+                apserv.swaprows(state.idensebatch, state.densebatchsize, kmax, n+1, _params);
+                apserv.swapelements(state.tmpnormestimates, state.densebatchsize, kmax, _params);
+                v = 1/vmax;
+                for(i_=0; i_<=n;i_++)
+                {
+                    state.pdensebatch[state.densebatchsize,i_] = v*state.pdensebatch[state.densebatchsize,i_];
+                }
+                v = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    vv = state.sdensebatch[state.densebatchsize,j]*state.s[j];
+                    v = v+vv*vv;
+                }
+                alglib.ap.assert((double)(v)>(double)(0), "SActiveSet.RebuildBasis(): integrity check failed, SNorm=0");
+                v = 1/Math.Sqrt(v);
+                for(i_=0; i_<=n;i_++)
+                {
+                    state.sdensebatch[state.densebatchsize,i_] = v*state.sdensebatch[state.densebatchsize,i_];
+                }
+                v = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    vv = state.idensebatch[state.densebatchsize,j];
+                    v = v+vv*vv;
+                }
+                alglib.ap.assert((double)(v)>(double)(0), "SActiveSet.RebuildBasis(): integrity check failed, INorm=0");
+                v = 1/Math.Sqrt(v);
+                for(i_=0; i_<=n;i_++)
+                {
+                    state.idensebatch[state.densebatchsize,i_] = v*state.idensebatch[state.densebatchsize,i_];
+                }
+                
+                //
+                // Reorthogonalize other candidates with respect to candidate #0:
+                // * calculate projections en masse with GEMV()
+                // * subtract projections with GER()
+                //
+                apserv.rvectorsetlengthatleast(ref state.tmp0, candidatescnt-1, _params);
+                for(j=0; j<=n-1; j++)
+                {
+                    state.tmpprodp[j] = state.pdensebatch[state.densebatchsize,j]/state.h[j];
+                    state.tmpprods[j] = state.sdensebatch[state.densebatchsize,j]*math.sqr(state.s[j]);
+                }
+                for(j=0; j<=n; j++)
+                {
+                    state.tmpcp[j] = state.pdensebatch[state.densebatchsize,j];
+                    state.tmpcs[j] = state.sdensebatch[state.densebatchsize,j];
+                    state.tmpci[j] = state.idensebatch[state.densebatchsize,j];
+                }
+                ablas.rmatrixgemv(candidatescnt-1, n, 1.0, state.pdensebatch, state.densebatchsize+1, 0, 0, state.tmpprodp, 0, 0.0, state.tmp0, 0, _params);
+                ablas.rmatrixger(candidatescnt-1, n+1, state.pdensebatch, state.densebatchsize+1, 0, -1.0, state.tmp0, 0, state.tmpcp, 0, _params);
+                ablas.rmatrixgemv(candidatescnt-1, n, 1.0, state.sdensebatch, state.densebatchsize+1, 0, 0, state.tmpprods, 0, 0.0, state.tmp0, 0, _params);
+                ablas.rmatrixger(candidatescnt-1, n+1, state.sdensebatch, state.densebatchsize+1, 0, -1.0, state.tmp0, 0, state.tmpcs, 0, _params);
+                ablas.rmatrixgemv(candidatescnt-1, n, 1.0, state.idensebatch, state.densebatchsize+1, 0, 0, state.tmpci, 0, 0.0, state.tmp0, 0, _params);
+                ablas.rmatrixger(candidatescnt-1, n+1, state.idensebatch, state.densebatchsize+1, 0, -1.0, state.tmp0, 0, state.tmpci, 0, _params);
+                
+                //
+                // Increase basis, decrease candidates count
+                //
+                apserv.inc(ref state.densebatchsize, _params);
+                apserv.dec(ref candidatescnt, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        This  function  appends new constraints (if possible; sometimes it isn't!)
+        to three orthonormal basises for current active set:
+        * P-orthogonal one, which is orthogonalized with inner product
+          (x,y) = x'*P*y, where P=inv(H) is current preconditioner
+        * S-orthogonal one, which is orthogonalized with inner product
+          (x,y) = x'*S'*S*y, where S is diagonal scaling matrix
+        * I-orthogonal one, which is orthogonalized with standard dot product
+
+        NOTE: all sets of orthogonal vectors are guaranteed  to  have  same  size.
+              P-orthogonal basis is built first, I/S-orthogonal basises are forced
+              to have same number of vectors as P-orthogonal one (padded  by  zero
+              vectors if needed).
+              
+        NOTE: this function may fail to update basis without  full  recalculation;
+              in such case it will set BasisIsReady to False and silently  return;
+              if it succeeds, it will increase BasisSize.
+
+        INPUT PARAMETERS:
+            State       -   active set object
+            NewEntries  -   array[N+NEC+NIC], indexes of constraints being  added
+                            are marked as True; it is responsibility of the caller
+                            to specify only those constraints which were previously
+                            inactive; when  some  constraint is  already  in   the
+                            active set, algorithm behavior is undefined.
+
+        OUTPUT PARAMETERS:
+            State   -   active set object with new basis
+            
+          -- ALGLIB --
+             Copyright 03.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sasappendtobasis(sactiveset state,
+            bool[] newentries,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int nec = 0;
+            int nic = 0;
+            int i = 0;
+            int j = 0;
+            int t = 0;
+            int nact = 0;
+            double v = 0;
+            double vp = 0;
+            double vs = 0;
+            double vi = 0;
+            double initnormp = 0;
+            double projnormp = 0;
+            double projnorms = 0;
+            double projnormi = 0;
+            int i_ = 0;
+
+            if( !state.basisisready )
+            {
+                return;
+            }
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            
+            //
+            // Count number of constraints to activate;
+            // perform integrity check.
+            //
+            nact = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( newentries[i] )
+                {
+                    nact = nact+1;
+                }
+            }
+            for(i=n; i<=n+nec-1; i++)
+            {
+                alglib.ap.assert(!newentries[i], "SAS: integrity check failed (appendtobasis.0)");
+            }
+            for(i=n+nec; i<=n+nec+nic-1; i++)
+            {
+                if( newentries[i] )
+                {
+                    nact = nact+1;
+                }
+            }
+            if( nact+state.basisage>maxbasisage )
+            {
+                state.basisisready = false;
+                return;
+            }
+            
+            //
+            // Resize basis matrices if needed
+            //
+            apserv.rmatrixgrowrowsto(ref state.pdensebatch, state.densebatchsize+nact, n+1, _params);
+            apserv.rmatrixgrowrowsto(ref state.sdensebatch, state.densebatchsize+nact, n+1, _params);
+            apserv.rmatrixgrowrowsto(ref state.idensebatch, state.densebatchsize+nact, n+1, _params);
+            
+            //
+            // Try adding recommended entries to basis.
+            // If reorthogonalization removes too much of candidate constraint,
+            // we will invalidate basis and try to rebuild it from scratch.
+            //
+            apserv.rvectorsetlengthatleast(ref state.tmp0, n+1, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmpcp, n+1, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmpcs, n+1, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmpci, n+1, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmpprodp, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmpprods, n, _params);
+            for(t=0; t<=n+nec+nic-1; t++)
+            {
+                if( newentries[t] )
+                {
+                    
+                    //
+                    // Basis is full? Quick skip!
+                    //
+                    if( state.sparsebatchsize+state.densebatchsize>=n )
+                    {
+                        alglib.ap.assert(state.sparsebatchsize+state.densebatchsize==n, "SAS: integrity check failed (sasappendtobasis)");
+                        break;
+                    }
+                    
+                    //
+                    // Copy constraint to temporary storage.
+                    //
+                    if( t<n )
+                    {
+                        
+                        //
+                        // Copy box constraint
+                        //
+                        for(j=0; j<=n; j++)
+                        {
+                            state.tmp0[j] = 0;
+                        }
+                        state.tmp0[t] = 1.0;
+                        state.tmp0[n] = state.xc[t];
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Copy general linear constraint
+                        //
+                        for(j=0; j<=n; j++)
+                        {
+                            state.tmp0[j] = state.cleic[t-n,j];
+                        }
+                    }
+                    
+                    //
+                    // Calculate initial norm (preconditioner is used for norm calculation).
+                    //
+                    initnormp = 0.0;
+                    for(j=0; j<=n-1; j++)
+                    {
+                        v = state.tmp0[j];
+                        initnormp = initnormp+v*v/state.h[j];
+                    }
+                    initnormp = Math.Sqrt(initnormp);
+                    if( (double)(initnormp)==(double)(0) )
+                    {
+                        
+                        //
+                        // Well, it is not expected. Let's just rebuild basis
+                        // from scratch and forget about this strange situation...
+                        //
+                        state.basisisready = false;
+                        return;
+                    }
+                    
+                    //
+                    // Orthogonalize Tmp0 w.r.t. sparse batch (box constraints stored in sparse storage).
+                    //
+                    // Copy to TmpCP/TmpCS/TmpCI (P for preconditioner-based inner product
+                    // used for orthogonalization, S for scale-based orthogonalization,
+                    // I for "traditional" inner product used for Gram-Schmidt orthogonalization).
+                    //
+                    for(i=0; i<=state.sparsebatchsize-1; i++)
+                    {
+                        j = state.sparsebatch[i];
+                        state.tmp0[n] = state.tmp0[n]-state.tmp0[j]*state.xc[j];
+                        state.tmp0[j] = 0.0;
+                    }
+                    for(j=0; j<=n; j++)
+                    {
+                        state.tmpcp[j] = state.tmp0[j];
+                        state.tmpcs[j] = state.tmp0[j];
+                        state.tmpci[j] = state.tmp0[j];
+                    }
+                    
+                    //
+                    // Orthogonalize TmpCP/S/I with respect to active linear constraints from dense batch.
+                    // Corresponding norm (preconditioner, scale, identity) is used in each case.
+                    //
+                    for(j=0; j<=n-1; j++)
+                    {
+                        state.tmpprodp[j] = 1/state.h[j];
+                        state.tmpprods[j] = math.sqr(state.s[j]);
+                    }
+                    for(i=0; i<=state.densebatchsize-1; i++)
+                    {
+                        vp = 0;
+                        vs = 0;
+                        vi = 0;
+                        for(j=0; j<=n-1; j++)
+                        {
+                            vp = vp+state.pdensebatch[i,j]*state.tmpcp[j]*state.tmpprodp[j];
+                            vs = vs+state.sdensebatch[i,j]*state.tmpcs[j]*state.tmpprods[j];
+                            vi = vi+state.idensebatch[i,j]*state.tmpci[j];
+                        }
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.tmpcp[i_] = state.tmpcp[i_] - vp*state.pdensebatch[i,i_];
+                        }
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.tmpcs[i_] = state.tmpcs[i_] - vs*state.sdensebatch[i,i_];
+                        }
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.tmpci[i_] = state.tmpci[i_] - vi*state.idensebatch[i,i_];
+                        }
+                    }
+                    projnormp = 0.0;
+                    projnorms = 0.0;
+                    projnormi = 0.0;
+                    for(j=0; j<=n-1; j++)
+                    {
+                        projnormp = projnormp+math.sqr(state.tmpcp[j])/state.h[j];
+                        projnorms = projnorms+math.sqr(state.tmpcs[j])*math.sqr(state.s[j]);
+                        projnormi = projnormi+math.sqr(state.tmpci[j]);
+                    }
+                    projnormp = Math.Sqrt(projnormp);
+                    projnorms = Math.Sqrt(projnorms);
+                    projnormi = Math.Sqrt(projnormi);
+                    if( (double)(projnormp)<=(double)(maxbasisdecay*initnormp) )
+                    {
+                        state.basisisready = false;
+                        return;
+                        
+                        //
+                        // Nearly zero row, skip
+                        //
+                    }
+                    alglib.ap.assert((double)(projnormp)>(double)(0), "SAS: integrity check failed, ProjNormP=0");
+                    alglib.ap.assert((double)(projnorms)>(double)(0), "SAS: integrity check failed, ProjNormS=0");
+                    alglib.ap.assert((double)(projnormi)>(double)(0), "SAS: integrity check failed, ProjNormI=0");
+                    v = 1/projnormp;
+                    for(i_=0; i_<=n;i_++)
+                    {
+                        state.pdensebatch[state.densebatchsize,i_] = v*state.tmpcp[i_];
+                    }
+                    v = 1/projnorms;
+                    for(i_=0; i_<=n;i_++)
+                    {
+                        state.sdensebatch[state.densebatchsize,i_] = v*state.tmpcs[i_];
+                    }
+                    v = 1/projnormi;
+                    for(i_=0; i_<=n;i_++)
+                    {
+                        state.idensebatch[state.densebatchsize,i_] = v*state.tmpci[i_];
+                    }
+                    
+                    //
+                    // Increase set size
+                    //
+                    apserv.inc(ref state.densebatchsize, _params);
+                    apserv.inc(ref state.basisage, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This  subroutine  calculates  preconditioned  descent direction subject to
+        current active set.
+
+        INPUT PARAMETERS:
+            State   -   active set object
+            G       -   array[N], gradient
+            H       -   array[N], Hessian matrix
+            HA      -   active constraints orthogonalized in such way
+                        that HA*inv(H)*HA'= I.
+            Normalize-  whether we need normalized descent or not
+            D       -   possibly preallocated buffer; automatically resized.
+            
+        OUTPUT PARAMETERS:
+            D       -   descent direction projected onto current active set.
+                        Components of D which correspond to active boundary
+                        constraints are forced to be exactly zero.
+                        In case D is non-zero and Normalize is True, it is
+                        normalized to have unit norm.
+                        
+        NOTE: if we have N active constraints, D is explicitly set to zero.
+
+          -- ALGLIB --
+             Copyright 21.12.2012 by Bochkanov Sergey
+        *************************************************************************/
+        private static void constraineddescent(sactiveset state,
+            double[] g,
+            double[] h,
+            double[,] ha,
+            bool normalize,
+            ref double[] d,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+            double v = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(state.algostate==1, "SAS: internal error in ConstrainedDescent() - not in optimization mode");
+            alglib.ap.assert(state.basisisready, "SAS: internal error in ConstrainedDescent() - no basis");
+            n = state.n;
+            apserv.rvectorsetlengthatleast(ref d, n, _params);
+            
+            //
+            // Calculate preconditioned constrained descent direction:
+            //
+            //     d := -inv(H)*( g - HA'*(HA*inv(H)*g) )
+            //
+            // Formula above always gives direction which is orthogonal to rows of HA.
+            // You can verify it by multiplication of both sides by HA[i] (I-th row),
+            // taking into account that HA*inv(H)*HA'= I (by definition of HA - it is
+            // orthogonal basis with inner product given by inv(H)).
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                d[i] = g[i];
+            }
+            for(i=0; i<=state.densebatchsize-1; i++)
+            {
+                v = 0.0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+ha[i,j]*d[j]/h[j];
+                }
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    d[i_] = d[i_] - v*ha[i,i_];
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.cstatus[i]>0 )
+                {
+                    d[i] = 0;
+                }
+            }
+            v = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                d[i] = -(d[i]/h[i]);
+                v = v+math.sqr(d[i]);
+            }
+            v = Math.Sqrt(v);
+            if( state.sparsebatchsize+state.densebatchsize>=n )
+            {
+                v = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    d[i] = 0;
+                }
+            }
+            if( normalize && (double)(v)>(double)(0) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    d[i] = d[i]/v;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function recalculates constraints - activates  and  deactivates  them
+        according to gradient value at current point.
+
+        Algorithm  assumes  that  we  want  to make Quasi-Newton step from current
+        point with diagonal Quasi-Newton matrix H. Constraints are  activated  and
+        deactivated in such way that we won't violate any constraint by step.
+
+        Only already "active" and "candidate" elements of ActiveSet are  examined;
+        constraints which are not active are not examined.
+
+        INPUT PARAMETERS:
+            State       -   active set object
+            GC          -   array[N], gradient at XC
+            H           -   array[N], Hessian matrix
+            
+        OUTPUT PARAMETERS:
+            State       -   active set object, with new set of constraint
+
+          -- ALGLIB --
+             Copyright 26.09.2012 by Bochkanov Sergey
+        *************************************************************************/
+        private static void reactivateconstraints(sactiveset state,
+            double[] gc,
+            double[] h,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int nec = 0;
+            int nic = 0;
+            int i = 0;
+            int j = 0;
+            int idx0 = 0;
+            int idx1 = 0;
+            double v = 0;
+            int nactivebnd = 0;
+            int nactivelin = 0;
+            int nactiveconstraints = 0;
+            double rowscale = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(state.algostate==1, "SASReactivateConstraintsPrec: must be in optimization mode");
+            
+            //
+            // Prepare
+            //
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            state.basisisready = false;
+            
+            //
+            // Handle important special case - no linear constraints,
+            // only boundary constraints are present
+            //
+            if( nec+nic==0 )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (state.hasbndl[i] && state.hasbndu[i]) && (double)(state.bndl[i])==(double)(state.bndu[i]) )
+                    {
+                        state.cstatus[i] = 1;
+                        continue;
+                    }
+                    if( (state.hasbndl[i] && (double)(state.xc[i])==(double)(state.bndl[i])) && (double)(gc[i])>=(double)(0) )
+                    {
+                        state.cstatus[i] = 1;
+                        continue;
+                    }
+                    if( (state.hasbndu[i] && (double)(state.xc[i])==(double)(state.bndu[i])) && (double)(gc[i])<=(double)(0) )
+                    {
+                        state.cstatus[i] = 1;
+                        continue;
+                    }
+                    state.cstatus[i] = -1;
+                }
+                return;
+            }
+            
+            //
+            // General case.
+            // Allocate temporaries.
+            //
+            apserv.rvectorsetlengthatleast(ref state.rctmpg, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.rctmprightpart, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.rctmps, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.rctmpdense0, n, nec+nic, _params);
+            apserv.rmatrixsetlengthatleast(ref state.rctmpdense1, n, nec+nic, _params);
+            apserv.bvectorsetlengthatleast(ref state.rctmpisequality, n+nec+nic, _params);
+            apserv.ivectorsetlengthatleast(ref state.rctmpconstraintidx, n+nec+nic, _params);
+            
+            //
+            // Calculate descent direction
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.rctmpg[i_] = -gc[i_];
+            }
+            
+            //
+            // Determine candidates to the active set.
+            //
+            // After this block constraints become either "inactive" (CStatus[i]<0)
+            // or "candidates" (CStatus[i]=0). Previously active constraints always
+            // become "candidates".
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.cstatus[i] = -1;
+            }
+            for(i=n; i<=n+nec+nic-1; i++)
+            {
+                if( state.cstatus[i]>0 )
+                {
+                    state.cstatus[i] = 0;
+                }
+                else
+                {
+                    state.cstatus[i] = -1;
+                }
+            }
+            nactiveconstraints = 0;
+            nactivebnd = 0;
+            nactivelin = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Activate boundary constraints:
+                // * copy constraint index to RCTmpConstraintIdx
+                // * set corresponding element of CStatus[] to "candidate"
+                // * fill RCTmpS by either +1 (lower bound) or -1 (upper bound)
+                // * set RCTmpIsEquality to False (BndL<BndU) or True (BndL=BndU)
+                // * increase counters
+                //
+                if( (state.hasbndl[i] && state.hasbndu[i]) && (double)(state.bndl[i])==(double)(state.bndu[i]) )
+                {
+                    
+                    //
+                    // Equality constraint is activated
+                    //
+                    state.rctmpconstraintidx[nactiveconstraints] = i;
+                    state.cstatus[i] = 0;
+                    state.rctmps[i] = 1.0;
+                    state.rctmpisequality[nactiveconstraints] = true;
+                    nactiveconstraints = nactiveconstraints+1;
+                    nactivebnd = nactivebnd+1;
+                    continue;
+                }
+                if( state.hasbndl[i] && (double)(state.xc[i])==(double)(state.bndl[i]) )
+                {
+                    
+                    //
+                    // Lower bound is activated
+                    //
+                    state.rctmpconstraintidx[nactiveconstraints] = i;
+                    state.cstatus[i] = 0;
+                    state.rctmps[i] = -1.0;
+                    state.rctmpisequality[nactiveconstraints] = false;
+                    nactiveconstraints = nactiveconstraints+1;
+                    nactivebnd = nactivebnd+1;
+                    continue;
+                }
+                if( state.hasbndu[i] && (double)(state.xc[i])==(double)(state.bndu[i]) )
+                {
+                    
+                    //
+                    // Upper bound is activated
+                    //
+                    state.rctmpconstraintidx[nactiveconstraints] = i;
+                    state.cstatus[i] = 0;
+                    state.rctmps[i] = 1.0;
+                    state.rctmpisequality[nactiveconstraints] = false;
+                    nactiveconstraints = nactiveconstraints+1;
+                    nactivebnd = nactivebnd+1;
+                    continue;
+                }
+            }
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                if( i>=nec && state.cstatus[n+i]<0 )
+                {
+                    
+                    //
+                    // Inequality constraints are skipped if both (a) constraint was
+                    // not active, and (b) we are too far away from the boundary.
+                    //
+                    rowscale = 0.0;
+                    v = -state.cleic[i,n];
+                    for(j=0; j<=n-1; j++)
+                    {
+                        v = v+state.cleic[i,j]*state.xc[j];
+                        rowscale = Math.Max(rowscale, Math.Abs(state.cleic[i,j]*state.s[j]));
+                    }
+                    if( (double)(v)<=(double)(-(1.0E5*math.machineepsilon*rowscale)) )
+                    {
+                        
+                        //
+                        // NOTE: it is important to check for non-strict inequality
+                        //       because we have to correctly handle zero constraint
+                        //       0*x<=0
+                        //
+                        continue;
+                    }
+                }
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.rctmpdense0[i_,nactivelin] = state.cleic[i,i_];
+                }
+                state.rctmpconstraintidx[nactiveconstraints] = n+i;
+                state.cstatus[n+i] = 0;
+                state.rctmpisequality[nactiveconstraints] = i<nec;
+                nactiveconstraints = nactiveconstraints+1;
+                nactivelin = nactivelin+1;
+            }
+            
+            //
+            // Skip if no "candidate" constraints was found
+            //
+            if( nactiveconstraints==0 )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (state.hasbndl[i] && state.hasbndu[i]) && (double)(state.bndl[i])==(double)(state.bndu[i]) )
+                    {
+                        state.cstatus[i] = 1;
+                        continue;
+                    }
+                    if( (state.hasbndl[i] && (double)(state.xc[i])==(double)(state.bndl[i])) && (double)(gc[i])>=(double)(0) )
+                    {
+                        state.cstatus[i] = 1;
+                        continue;
+                    }
+                    if( (state.hasbndu[i] && (double)(state.xc[i])==(double)(state.bndu[i])) && (double)(gc[i])<=(double)(0) )
+                    {
+                        state.cstatus[i] = 1;
+                        continue;
+                    }
+                }
+                return;
+            }
+            
+            //
+            // General case.
+            //
+            // APPROACH TO CONSTRAINTS ACTIVATION/DEACTIVATION
+            //
+            // We have NActiveConstraints "candidates": NActiveBnd boundary candidates,
+            // NActiveLin linear candidates. Indexes of boundary constraints are stored
+            // in RCTmpConstraintIdx[0:NActiveBnd-1], indexes of linear ones are stored
+            // in RCTmpConstraintIdx[NActiveBnd:NActiveBnd+NActiveLin-1]. Some of the
+            // constraints are equality ones, some are inequality - as specified by 
+            // RCTmpIsEquality[i].
+            //
+            // Now we have to determine active subset of "candidates" set. In order to
+            // do so we solve following constrained minimization problem:
+            //         (                         )^2
+            //     min ( SUM(lambda[i]*A[i]) + G )
+            //         (                         )
+            // Here:
+            // * G is a gradient (column vector)
+            // * A[i] is a column vector, linear (left) part of I-th constraint.
+            //   I=0..NActiveConstraints-1, first NActiveBnd elements of A are just
+            //   subset of identity matrix (boundary constraints), next NActiveLin
+            //   elements are subset of rows of the matrix of general linear constraints.
+            // * lambda[i] is a Lagrange multiplier corresponding to I-th constraint
+            //
+            // NOTE: for preconditioned setting A is replaced by A*H^(-0.5), G is
+            //       replaced by G*H^(-0.5). We apply this scaling at the last stage,
+            //       before passing data to NNLS solver.
+            //
+            // Minimization is performed subject to non-negativity constraints on
+            // lambda[i] corresponding to inequality constraints. Inequality constraints
+            // which correspond to non-zero lambda are activated, equality constraints
+            // are always considered active.
+            //
+            // Informally speaking, we "decompose" descent direction -G and represent
+            // it as sum of constraint vectors and "residual" part (which is equal to
+            // the actual descent direction subject to constraints).
+            //
+            // SOLUTION OF THE NNLS PROBLEM
+            //
+            // We solve this optimization problem with Non-Negative Least Squares solver,
+            // which can efficiently solve least squares problems of the form
+            //
+            //         ( [ I | AU ]     )^2
+            //     min ( [   |    ]*x-b )   s.t. non-negativity constraints on some x[i]
+            //         ( [ 0 | AL ]     )
+            //
+            // In order to use this solver we have to rearrange rows of A[] and G in
+            // such way that first NActiveBnd columns of A store identity matrix (before
+            // sorting non-zero elements are randomly distributed in the first NActiveBnd
+            // columns of A, during sorting we move them to first NActiveBnd rows).
+            //
+            // Then we create instance of NNLS solver (we reuse instance left from the
+            // previous run of the optimization problem) and solve NNLS problem.
+            //
+            idx0 = 0;
+            idx1 = nactivebnd;
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.cstatus[i]>=0 )
+                {
+                    v = 1/Math.Sqrt(h[i]);
+                    for(j=0; j<=nactivelin-1; j++)
+                    {
+                        state.rctmpdense1[idx0,j] = state.rctmpdense0[i,j]/state.rctmps[i]*v;
+                    }
+                    state.rctmprightpart[idx0] = state.rctmpg[i]/state.rctmps[i]*v;
+                    idx0 = idx0+1;
+                }
+                else
+                {
+                    v = 1/Math.Sqrt(h[i]);
+                    for(j=0; j<=nactivelin-1; j++)
+                    {
+                        state.rctmpdense1[idx1,j] = state.rctmpdense0[i,j]*v;
+                    }
+                    state.rctmprightpart[idx1] = state.rctmpg[i]*v;
+                    idx1 = idx1+1;
+                }
+            }
+            snnls.snnlsinit(n, Math.Min(nec+nic, n), n, state.solver, _params);
+            snnls.snnlssetproblem(state.solver, state.rctmpdense1, state.rctmprightpart, nactivebnd, nactiveconstraints-nactivebnd, n, _params);
+            for(i=0; i<=nactiveconstraints-1; i++)
+            {
+                if( state.rctmpisequality[i] )
+                {
+                    snnls.snnlsdropnnc(state.solver, i, _params);
+                }
+            }
+            snnls.snnlssolve(state.solver, ref state.rctmplambdas, _params);
+            
+            //
+            // After solution of the problem we activate equality constraints (always active)
+            // and inequality constraints with non-zero Lagrange multipliers. Then we reorthogonalize
+            // active constraints.
+            //
+            for(i=0; i<=n+nec+nic-1; i++)
+            {
+                state.cstatus[i] = -1;
+            }
+            for(i=0; i<=nactiveconstraints-1; i++)
+            {
+                if( state.rctmpisequality[i] || (double)(state.rctmplambdas[i])>(double)(0) )
+                {
+                    state.cstatus[state.rctmpconstraintidx[i]] = 1;
+                }
+                else
+                {
+                    state.cstatus[state.rctmpconstraintidx[i]] = 0;
+                }
+            }
+            sasrebuildbasis(state, _params);
+        }
+
+
+    }
+    public class qqpsolver
+    {
+        /*************************************************************************
+        This object stores settings for QQP solver.
+        It must be initialized with QQPLoadDefaults().
+        After initialization you may change settings.
+        *************************************************************************/
+        public class qqpsettings : apobject
+        {
+            public double epsg;
+            public double epsf;
+            public double epsx;
+            public int maxouterits;
+            public bool cgphase;
+            public bool cnphase;
+            public int cgminits;
+            public int cgmaxits;
+            public int cnmaxupdates;
+            public int sparsesolver;
+            public qqpsettings()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                qqpsettings _result = new qqpsettings();
+                _result.epsg = epsg;
+                _result.epsf = epsf;
+                _result.epsx = epsx;
+                _result.maxouterits = maxouterits;
+                _result.cgphase = cgphase;
+                _result.cnphase = cnphase;
+                _result.cgminits = cgminits;
+                _result.cgmaxits = cgmaxits;
+                _result.cnmaxupdates = cnmaxupdates;
+                _result.sparsesolver = sparsesolver;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This object stores temporaries used by QuickQP solver.
+        *************************************************************************/
+        public class qqpbuffers : apobject
+        {
+            public int n;
+            public int akind;
+            public double[,] densea;
+            public sparse.sparsematrix sparsea;
+            public bool sparseupper;
+            public double absamax;
+            public double absasum;
+            public double absasum2;
+            public double[] b;
+            public double[] bndl;
+            public double[] bndu;
+            public bool[] havebndl;
+            public bool[] havebndu;
+            public double[] xs;
+            public double[] xf;
+            public double[] gc;
+            public double[] xp;
+            public double[] dc;
+            public double[] dp;
+            public double[] cgc;
+            public double[] cgp;
+            public sactivesets.sactiveset sas;
+            public bool[] activated;
+            public int nfree;
+            public int cnmodelage;
+            public double[,] densez;
+            public sparse.sparsematrix sparsecca;
+            public int[] yidx;
+            public double[] regdiag;
+            public double[] regx0;
+            public double[] tmpcn;
+            public int[] tmpcni;
+            public bool[] tmpcnb;
+            public double[] tmp0;
+            public double[] tmp1;
+            public double[] stpbuf;
+            public sparse.sparsebuffers sbuf;
+            public int repinneriterationscount;
+            public int repouteriterationscount;
+            public int repncholesky;
+            public int repncupdates;
+            public qqpbuffers()
+            {
+                init();
+            }
+            public override void init()
+            {
+                densea = new double[0,0];
+                sparsea = new sparse.sparsematrix();
+                b = new double[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                havebndl = new bool[0];
+                havebndu = new bool[0];
+                xs = new double[0];
+                xf = new double[0];
+                gc = new double[0];
+                xp = new double[0];
+                dc = new double[0];
+                dp = new double[0];
+                cgc = new double[0];
+                cgp = new double[0];
+                sas = new sactivesets.sactiveset();
+                activated = new bool[0];
+                densez = new double[0,0];
+                sparsecca = new sparse.sparsematrix();
+                yidx = new int[0];
+                regdiag = new double[0];
+                regx0 = new double[0];
+                tmpcn = new double[0];
+                tmpcni = new int[0];
+                tmpcnb = new bool[0];
+                tmp0 = new double[0];
+                tmp1 = new double[0];
+                stpbuf = new double[0];
+                sbuf = new sparse.sparsebuffers();
+            }
+            public override alglib.apobject make_copy()
+            {
+                qqpbuffers _result = new qqpbuffers();
+                _result.n = n;
+                _result.akind = akind;
+                _result.densea = (double[,])densea.Clone();
+                _result.sparsea = (sparse.sparsematrix)sparsea.make_copy();
+                _result.sparseupper = sparseupper;
+                _result.absamax = absamax;
+                _result.absasum = absasum;
+                _result.absasum2 = absasum2;
+                _result.b = (double[])b.Clone();
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.havebndl = (bool[])havebndl.Clone();
+                _result.havebndu = (bool[])havebndu.Clone();
+                _result.xs = (double[])xs.Clone();
+                _result.xf = (double[])xf.Clone();
+                _result.gc = (double[])gc.Clone();
+                _result.xp = (double[])xp.Clone();
+                _result.dc = (double[])dc.Clone();
+                _result.dp = (double[])dp.Clone();
+                _result.cgc = (double[])cgc.Clone();
+                _result.cgp = (double[])cgp.Clone();
+                _result.sas = (sactivesets.sactiveset)sas.make_copy();
+                _result.activated = (bool[])activated.Clone();
+                _result.nfree = nfree;
+                _result.cnmodelage = cnmodelage;
+                _result.densez = (double[,])densez.Clone();
+                _result.sparsecca = (sparse.sparsematrix)sparsecca.make_copy();
+                _result.yidx = (int[])yidx.Clone();
+                _result.regdiag = (double[])regdiag.Clone();
+                _result.regx0 = (double[])regx0.Clone();
+                _result.tmpcn = (double[])tmpcn.Clone();
+                _result.tmpcni = (int[])tmpcni.Clone();
+                _result.tmpcnb = (bool[])tmpcnb.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmp1 = (double[])tmp1.Clone();
+                _result.stpbuf = (double[])stpbuf.Clone();
+                _result.sbuf = (sparse.sparsebuffers)sbuf.make_copy();
+                _result.repinneriterationscount = repinneriterationscount;
+                _result.repouteriterationscount = repouteriterationscount;
+                _result.repncholesky = repncholesky;
+                _result.repncupdates = repncupdates;
+                return _result;
+            }
+        };
+
+
+
+
+        public const int quickqprestartcg = 50;
+        public const double regz = 1.0E-9;
+
+
+        /*************************************************************************
+        This function initializes QQPSettings structure with default settings.
+
+        Newly created structure MUST be initialized by default settings  -  or  by
+        copy of the already initialized structure.
+
+          -- ALGLIB --
+             Copyright 14.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void qqploaddefaults(int n,
+            qqpsettings s,
+            alglib.xparams _params)
+        {
+            s.epsg = 0.0;
+            s.epsf = 0.0;
+            s.epsx = 1.0E-6;
+            s.maxouterits = 0;
+            s.cgphase = true;
+            s.cnphase = true;
+            s.cgminits = 5;
+            s.cgmaxits = Math.Max(s.cgminits, (int)Math.Round(1+0.33*n));
+            s.sparsesolver = 0;
+            s.cnmaxupdates = (int)Math.Round(1+0.1*n);
+        }
+
+
+        /*************************************************************************
+        This function initializes QQPSettings  structure  with  copy  of  another,
+        already initialized structure.
+
+          -- ALGLIB --
+             Copyright 14.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void qqpcopysettings(qqpsettings src,
+            qqpsettings dst,
+            alglib.xparams _params)
+        {
+            dst.epsg = src.epsg;
+            dst.epsf = src.epsf;
+            dst.epsx = src.epsx;
+            dst.maxouterits = src.maxouterits;
+            dst.cgphase = src.cgphase;
+            dst.cnphase = src.cnphase;
+            dst.cgminits = src.cgminits;
+            dst.cgmaxits = src.cgmaxits;
+            dst.sparsesolver = src.sparsesolver;
+            dst.cnmaxupdates = src.cnmaxupdates;
+        }
+
+
+        /*************************************************************************
+        This function performs preallocation of internal 2D matrices. If matrix
+        size is less than expected, we grow to some larger value (specified by user).
+
+        It can be useful in cases when we solve many subsequent QP problems with
+        increasing sizes - helps to avoid multiple allocations.
+
+        INPUT PARAMETERS:
+            SState      -   object which stores temporaries:
+                            * uninitialized object is automatically initialized
+                            * previously allocated memory is reused as much
+                              as possible
+            NExpected   -   if internal buffers have size enough for NExpected,
+                            no preallocation happens. If size is less than NExpected,
+                            buffers are preallocated up to NGrowTo*NGrowTo
+            NGrowTo     -   new size
+            
+            
+        OUTPUT PARAMETERS:
+            SState      -   temporary buffers, some of them are preallocated
+
+          -- ALGLIB --
+             Copyright 09.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void qqppreallocategrowdense(qqpbuffers sstate,
+            int nexpected,
+            int ngrowto,
+            alglib.xparams _params)
+        {
+            if( alglib.ap.rows(sstate.densea)<nexpected || alglib.ap.cols(sstate.densea)<nexpected )
+            {
+                apserv.rmatrixsetlengthatleast(ref sstate.densea, ngrowto, ngrowto, _params);
+            }
+            if( alglib.ap.rows(sstate.densez)<nexpected || alglib.ap.cols(sstate.densez)<nexpected )
+            {
+                apserv.rmatrixsetlengthatleast(ref sstate.densez, ngrowto, ngrowto, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        This function runs QQP solver; it returns after optimization  process  was
+        completed. Following QP problem is solved:
+
+            min(0.5*(x-x_origin)'*A*(x-x_origin)+b'*(x-x_origin))
+            
+        subject to boundary constraints.
+
+        IMPORTANT: UNLIKE MANY OTHER SOLVERS, THIS FUNCTION DOES NOT  REQUIRE  YOU
+                   TO INITIALIZE STATE OBJECT. IT CAN BE AUTOMATICALLY INITIALIZED
+                   DURING SOLUTION PROCESS.
+
+        INPUT PARAMETERS:
+            AC          -   for dense problems given by CQM model (AKind=0) A-term
+                            of CQM object contains system matrix. Other terms  are
+                            unspecified and should not be referenced.
+            SparseAC    -   for sparse problems (AKind=1)
+            DenseAC     -   for traditional dense matrices (AKind=2)
+            AKind       -   matrix term to use:
+                            * 0 for dense CQM (CQMAC)
+                            * 1 for sparse matrix (SparseAC)
+                            * 2 for dense matrix (DenseAC)
+            IsUpper     -   which triangle of  SparseAC/DenseAC  stores  matrix  -
+                            upper or lower one (for dense matrices this  parameter
+                            is not actual).
+            BC          -   linear term, array[NC]
+            BndLC       -   lower bound, array[NC]
+            BndUC       -   upper bound, array[NC]
+            SC          -   scale vector, array[NC]:
+                            * I-th element contains scale of I-th variable,
+                            * SC[I]>0
+            XOriginC    -   origin term, array[NC]. Can be zero.
+            NC          -   number of variables in the  original  formulation  (no
+                            slack variables).
+            CLEICC      -   linear equality/inequality constraints. Present version
+                            of this function does NOT provide  publicly  available
+                            support for linear constraints. This feature  will  be
+                            introduced in the future versions of the function.
+            NEC, NIC    -   number of equality/inequality constraints.
+                            MUST BE ZERO IN THE CURRENT VERSION!!!
+            Settings    -   QQPSettings object initialized by one of the initialization
+                            functions.
+            SState      -   object which stores temporaries:
+                            * uninitialized object is automatically initialized
+                            * previously allocated memory is reused as much
+                              as possible
+            XS          -   initial point, array[NC]
+            
+            
+        OUTPUT PARAMETERS:
+            XS          -   last point
+            TerminationType-termination type:
+                            *
+                            *
+                            *
+
+          -- ALGLIB --
+             Copyright 14.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void qqpoptimize(cqmodels.convexquadraticmodel cqmac,
+            sparse.sparsematrix sparseac,
+            double[,] denseac,
+            int akind,
+            bool isupper,
+            double[] bc,
+            double[] bndlc,
+            double[] bnduc,
+            double[] sc,
+            double[] xoriginc,
+            int nc,
+            qqpsettings settings,
+            qqpbuffers sstate,
+            double[] xs,
+            ref int terminationtype,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            double vv = 0;
+            double d2 = 0;
+            double d1 = 0;
+            int d1est = 0;
+            int d2est = 0;
+            bool needact = new bool();
+            double reststp = 0;
+            double fullstp = 0;
+            double stpmax = 0;
+            double stp = 0;
+            int stpcnt = 0;
+            int cidx = 0;
+            double cval = 0;
+            int cgcnt = 0;
+            int cgmax = 0;
+            int newtcnt = 0;
+            int sparsesolver = 0;
+            double beta = 0;
+            bool b = new bool();
+            double fprev = 0;
+            double fcur = 0;
+            bool problemsolved = new bool();
+            bool isconstrained = new bool();
+            double f0 = 0;
+            double f1 = 0;
+            int i_ = 0;
+
+            terminationtype = 0;
+
+            
+            //
+            // Primary checks
+            //
+            alglib.ap.assert((akind==0 || akind==1) || akind==2, "QQPOptimize: incorrect AKind");
+            sstate.n = nc;
+            n = sstate.n;
+            terminationtype = 0;
+            sstate.repinneriterationscount = 0;
+            sstate.repouteriterationscount = 0;
+            sstate.repncholesky = 0;
+            sstate.repncupdates = 0;
+            
+            //
+            // Several checks
+            // * matrix size
+            // * scale vector
+            // * consistency of bound constraints
+            // * consistency of settings
+            //
+            if( akind==1 )
+            {
+                alglib.ap.assert(sparse.sparsegetnrows(sparseac, _params)==n, "QQPOptimize: rows(SparseAC)<>N");
+                alglib.ap.assert(sparse.sparsegetncols(sparseac, _params)==n, "QQPOptimize: cols(SparseAC)<>N");
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(sc[i]) && (double)(sc[i])>(double)(0), "QQPOptimize: incorrect scale");
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( math.isfinite(bndlc[i]) && math.isfinite(bnduc[i]) )
+                {
+                    if( (double)(bndlc[i])>(double)(bnduc[i]) )
+                    {
+                        terminationtype = -3;
+                        return;
+                    }
+                }
+            }
+            alglib.ap.assert(settings.cgphase || settings.cnphase, "QQPOptimize: both phases (CG and Newton) are inactive");
+            
+            //
+            // Allocate data structures
+            //
+            apserv.rvectorsetlengthatleast(ref sstate.bndl, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.bndu, n, _params);
+            apserv.bvectorsetlengthatleast(ref sstate.havebndl, n, _params);
+            apserv.bvectorsetlengthatleast(ref sstate.havebndu, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.xs, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.xf, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.xp, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.gc, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.cgc, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.cgp, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.dc, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.dp, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.tmp0, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.tmp1, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.stpbuf, 15, _params);
+            sactivesets.sasinit(n, sstate.sas, _params);
+            
+            //
+            // Scale/shift problem coefficients:
+            //
+            //     min { 0.5*(x-x0)'*A*(x-x0) + b'*(x-x0) }
+            //
+            // becomes (after transformation "x = S*y+x0")
+            //
+            //     min { 0.5*y'*(S*A*S)*y + (S*b)'*y
+            //
+            // Modified A_mod=S*A*S and b_mod=S*(b+A*x0) are
+            // stored into SState.DenseA and SState.B.
+            //       
+            //
+            apserv.rvectorsetlengthatleast(ref sstate.b, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                sstate.b[i] = sc[i]*bc[i];
+            }
+            sstate.akind = -99;
+            if( akind==0 )
+            {
+                
+                //
+                // Dense QP problem - just copy and scale.
+                //
+                apserv.rmatrixsetlengthatleast(ref sstate.densea, n, n, _params);
+                cqmodels.cqmgeta(cqmac, ref sstate.densea, _params);
+                sstate.akind = 0;
+                sstate.absamax = 0;
+                sstate.absasum = 0;
+                sstate.absasum2 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        v = sc[i]*sstate.densea[i,j]*sc[j];
+                        vv = Math.Abs(v);
+                        sstate.densea[i,j] = v;
+                        sstate.absamax = Math.Max(sstate.absamax, vv);
+                        sstate.absasum = sstate.absasum+vv;
+                        sstate.absasum2 = sstate.absasum2+vv*vv;
+                    }
+                }
+            }
+            if( akind==1 )
+            {
+                
+                //
+                // Sparse QP problem - a bit tricky. Depending on format of the
+                // input we use different strategies for copying matrix:
+                // * SKS matrices are copied to SKS format
+                // * anything else is copied to CRS format
+                //
+                sparse.sparsecopytosksbuf(sparseac, sstate.sparsea, _params);
+                if( isupper )
+                {
+                    sparse.sparsetransposesks(sstate.sparsea, _params);
+                }
+                sstate.akind = 1;
+                sstate.sparseupper = false;
+                sstate.absamax = 0;
+                sstate.absasum = 0;
+                sstate.absasum2 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    k = sstate.sparsea.ridx[i];
+                    for(j=i-sstate.sparsea.didx[i]; j<=i; j++)
+                    {
+                        v = sc[i]*sstate.sparsea.vals[k]*sc[j];
+                        vv = Math.Abs(v);
+                        sstate.sparsea.vals[k] = v;
+                        if( i==j )
+                        {
+                            
+                            //
+                            // Diagonal terms are counted only once
+                            //
+                            sstate.absamax = Math.Max(sstate.absamax, vv);
+                            sstate.absasum = sstate.absasum+vv;
+                            sstate.absasum2 = sstate.absasum2+vv*vv;
+                        }
+                        else
+                        {
+                            
+                            //
+                            // Offdiagonal terms are counted twice
+                            //
+                            sstate.absamax = Math.Max(sstate.absamax, vv);
+                            sstate.absasum = sstate.absasum+2*vv;
+                            sstate.absasum2 = sstate.absasum2+2*vv*vv;
+                        }
+                        k = k+1;
+                    }
+                }
+            }
+            if( akind==2 )
+            {
+                
+                //
+                // Dense QP problem - just copy and scale.
+                //
+                apserv.rmatrixsetlengthatleast(ref sstate.densea, n, n, _params);
+                sstate.akind = 0;
+                sstate.absamax = 0;
+                sstate.absasum = 0;
+                sstate.absasum2 = 0;
+                if( isupper )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=i; j<=n-1; j++)
+                        {
+                            v = sc[i]*denseac[i,j]*sc[j];
+                            vv = Math.Abs(v);
+                            sstate.densea[i,j] = v;
+                            sstate.densea[j,i] = v;
+                            if( (double)(i)==(double)(v) )
+                            {
+                                k = 1;
+                            }
+                            else
+                            {
+                                k = 2;
+                            }
+                            sstate.absamax = Math.Max(sstate.absamax, vv);
+                            sstate.absasum = sstate.absasum+vv*k;
+                            sstate.absasum2 = sstate.absasum2+vv*vv*k;
+                        }
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=0; j<=i; j++)
+                        {
+                            v = sc[i]*denseac[i,j]*sc[j];
+                            vv = Math.Abs(v);
+                            sstate.densea[i,j] = v;
+                            sstate.densea[j,i] = v;
+                            if( (double)(i)==(double)(v) )
+                            {
+                                k = 1;
+                            }
+                            else
+                            {
+                                k = 2;
+                            }
+                            sstate.absamax = Math.Max(sstate.absamax, vv);
+                            sstate.absasum = sstate.absasum+vv*k;
+                            sstate.absasum2 = sstate.absasum2+vv*vv*k;
+                        }
+                    }
+                }
+            }
+            alglib.ap.assert(sstate.akind>=0, "QQP: integrity check failed");
+            
+            //
+            // Load box constraints into State structure.
+            //
+            // We apply transformation to variables: y=(x-x_origin)/s,
+            // each of the constraints is appropriately shifted/scaled.
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                sstate.havebndl[i] = math.isfinite(bndlc[i]);
+                if( sstate.havebndl[i] )
+                {
+                    sstate.bndl[i] = (bndlc[i]-xoriginc[i])/sc[i];
+                }
+                else
+                {
+                    alglib.ap.assert(Double.IsNegativeInfinity(bndlc[i]), "QQPOptimize: incorrect lower bound");
+                    sstate.bndl[i] = Double.NegativeInfinity;
+                }
+                sstate.havebndu[i] = math.isfinite(bnduc[i]);
+                if( sstate.havebndu[i] )
+                {
+                    sstate.bndu[i] = (bnduc[i]-xoriginc[i])/sc[i];
+                }
+                else
+                {
+                    alglib.ap.assert(Double.IsPositiveInfinity(bnduc[i]), "QQPOptimize: incorrect upper bound");
+                    sstate.bndu[i] = Double.PositiveInfinity;
+                }
+            }
+            
+            //
+            // Process initial point:
+            // * set it to XS-XOriginC
+            // * make sure that boundary constraints are preserved by transformation
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                sstate.xs[i] = (xs[i]-xoriginc[i])/sc[i];
+                if( sstate.havebndl[i] && (double)(sstate.xs[i])<(double)(sstate.bndl[i]) )
+                {
+                    sstate.xs[i] = sstate.bndl[i];
+                }
+                if( sstate.havebndu[i] && (double)(sstate.xs[i])>(double)(sstate.bndu[i]) )
+                {
+                    sstate.xs[i] = sstate.bndu[i];
+                }
+                if( sstate.havebndl[i] && (double)(xs[i])==(double)(bndlc[i]) )
+                {
+                    sstate.xs[i] = sstate.bndl[i];
+                }
+                if( sstate.havebndu[i] && (double)(xs[i])==(double)(bnduc[i]) )
+                {
+                    sstate.xs[i] = sstate.bndu[i];
+                }
+            }
+            
+            //
+            // Select sparse direct solver
+            //
+            if( akind==1 )
+            {
+                sparsesolver = settings.sparsesolver;
+                if( sparsesolver==0 )
+                {
+                    sparsesolver = 1;
+                }
+                if( sparse.sparseissks(sstate.sparsea, _params) )
+                {
+                    sparsesolver = 2;
+                }
+                sparsesolver = 2;
+                alglib.ap.assert(sparsesolver==1 || sparsesolver==2, "QQPOptimize: incorrect SparseSolver");
+            }
+            else
+            {
+                sparsesolver = 0;
+            }
+            
+            //
+            // For unconstrained problems - try to use fast approach which requires
+            // just one unregularized Cholesky decomposition for solution. If it fails,
+            // switch to general QQP code.
+            //
+            problemsolved = false;
+            isconstrained = false;
+            for(i=0; i<=n-1; i++)
+            {
+                isconstrained = (isconstrained || sstate.havebndl[i]) || sstate.havebndu[i];
+            }
+            if( (!isconstrained && settings.cnphase) && akind==0 )
+            {
+                apserv.rmatrixsetlengthatleast(ref sstate.densez, n, n, _params);
+                apserv.rvectorsetlengthatleast(ref sstate.tmpcn, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=i; j<=n-1; j++)
+                    {
+                        sstate.densez[i,j] = sstate.densea[i,j];
+                    }
+                }
+                apserv.inc(ref sstate.repncholesky, _params);
+                if( trfac.spdmatrixcholeskyrec(ref sstate.densez, 0, n, true, ref sstate.tmpcn, _params) )
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        sstate.xf[i_] = sstate.xs[i_];
+                    }
+                    for(i=0; i<=n-1; i++)
+                    {
+                        sstate.dc[i] = 0;
+                    }
+                    f0 = projectedtargetfunction(sstate, sstate.xf, sstate.dc, 0.0, ref sstate.tmpcn, ref sstate.tmp1, _params);
+                    for(k=0; k<=3; k++)
+                    {
+                        ablas.rmatrixmv(n, n, sstate.densea, 0, 0, 0, sstate.xf, 0, sstate.gc, 0, _params);
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            sstate.gc[i_] = sstate.gc[i_] + sstate.b[i_];
+                        }
+                        for(i=0; i<=n-1; i++)
+                        {
+                            sstate.dc[i] = -sstate.gc[i];
+                        }
+                        fbls.fblscholeskysolve(sstate.densez, 1.0, n, true, sstate.dc, ref sstate.tmpcn, _params);
+                        f1 = projectedtargetfunction(sstate, sstate.xf, sstate.dc, 1.0, ref sstate.tmpcn, ref sstate.tmp1, _params);
+                        if( (double)(f1)>=(double)(f0) )
+                        {
+                            break;
+                        }
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            sstate.xf[i_] = sstate.xf[i_] + sstate.dc[i_];
+                        }
+                        f0 = f1;
+                    }
+                    terminationtype = 2;
+                    problemsolved = true;
+                }
+            }
+            
+            //
+            // Attempt to solve problem with fast approach failed, use generic QQP
+            //
+            if( !problemsolved )
+            {
+                
+                //
+                // Prepare "active set" structure
+                //
+                sactivesets.sassetbc(sstate.sas, sstate.bndl, sstate.bndu, _params);
+                if( !sactivesets.sasstartoptimization(sstate.sas, sstate.xs, _params) )
+                {
+                    terminationtype = -3;
+                    return;
+                }
+                
+                //
+                // Main loop.
+                //
+                // Following variables are used:
+                // * GC stores current gradient (unconstrained)
+                // * CGC stores current gradient (constrained)
+                // * DC stores current search direction
+                // * CGP stores constrained gradient at previous point
+                //   (zero on initial entry)
+                // * DP stores previous search direction
+                //   (zero on initial entry)
+                //
+                cgmax = settings.cgminits;
+                sstate.repinneriterationscount = 0;
+                sstate.repouteriterationscount = 0;
+                while( true )
+                {
+                    if( settings.maxouterits>0 && sstate.repouteriterationscount>=settings.maxouterits )
+                    {
+                        terminationtype = 5;
+                        break;
+                    }
+                    if( sstate.repouteriterationscount>0 )
+                    {
+                        
+                        //
+                        // Check EpsF- and EpsX-based stopping criteria.
+                        // Because problem was already scaled, we do not scale step before checking its length.
+                        // NOTE: these checks are performed only after at least one outer iteration was made.
+                        //
+                        if( (double)(settings.epsf)>(double)(0) )
+                        {
+                            
+                            //
+                            // NOTE 1: here we rely on the fact that ProjectedTargetFunction() ignore D when Stp=0
+                            // NOTE 2: code below handles situation when update increases function value instead
+                            //         of decreasing it.
+                            //
+                            fprev = projectedtargetfunction(sstate, sstate.xp, sstate.dc, 0.0, ref sstate.tmp0, ref sstate.tmp1, _params);
+                            fcur = projectedtargetfunction(sstate, sstate.sas.xc, sstate.dc, 0.0, ref sstate.tmp0, ref sstate.tmp1, _params);
+                            if( (double)(fprev-fcur)<=(double)(settings.epsf*Math.Max(Math.Abs(fprev), Math.Max(Math.Abs(fcur), 1.0))) )
+                            {
+                                terminationtype = 1;
+                                break;
+                            }
+                        }
+                        if( (double)(settings.epsx)>(double)(0) )
+                        {
+                            v = 0.0;
+                            for(i=0; i<=n-1; i++)
+                            {
+                                v = v+math.sqr(sstate.xp[i]-sstate.sas.xc[i]);
+                            }
+                            if( (double)(Math.Sqrt(v))<=(double)(settings.epsx) )
+                            {
+                                terminationtype = 2;
+                                break;
+                            }
+                        }
+                    }
+                    apserv.inc(ref sstate.repouteriterationscount, _params);
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        sstate.xp[i_] = sstate.sas.xc[i_];
+                    }
+                    if( !settings.cgphase )
+                    {
+                        cgmax = 0;
+                    }
+                    for(i=0; i<=n-1; i++)
+                    {
+                        sstate.cgp[i] = 0.0;
+                        sstate.dp[i] = 0.0;
+                    }
+                    for(cgcnt=0; cgcnt<=cgmax-1; cgcnt++)
+                    {
+                        
+                        //
+                        // Calculate unconstrained gradient GC for "extended" QP problem
+                        // Determine active set, current constrained gradient CGC.
+                        // Check gradient-based stopping condition.
+                        //
+                        // NOTE: because problem was scaled, we do not have to apply scaling
+                        //       to gradient before checking stopping condition.
+                        //
+                        targetgradient(sstate, sstate.sas.xc, ref sstate.gc, _params);
+                        sactivesets.sasreactivateconstraints(sstate.sas, sstate.gc, _params);
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            sstate.cgc[i_] = sstate.gc[i_];
+                        }
+                        sactivesets.sasconstraineddirection(sstate.sas, ref sstate.cgc, _params);
+                        v = 0.0;
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            v += sstate.cgc[i_]*sstate.cgc[i_];
+                        }
+                        if( (double)(Math.Sqrt(v))<=(double)(settings.epsg) )
+                        {
+                            terminationtype = 4;
+                            break;
+                        }
+                        
+                        //
+                        // Prepare search direction DC and explore it.
+                        //
+                        // We try to use CGP/DP to prepare conjugate gradient step,
+                        // but we resort to steepest descent step (Beta=0) in case
+                        // we are at I-th boundary, but DP[I]<>0.
+                        //
+                        // Such approach allows us to ALWAYS have feasible DC, with
+                        // guaranteed compatibility with both feasible area and current
+                        // active set.
+                        //
+                        // Automatic CG reset performed every time DP is incompatible
+                        // with current active set and/or feasible area. We also
+                        // perform reset every QuickQPRestartCG iterations.
+                        //
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            sstate.dc[i_] = -sstate.cgc[i_];
+                        }
+                        v = 0.0;
+                        vv = 0.0;
+                        b = false;
+                        for(i=0; i<=n-1; i++)
+                        {
+                            v = v+sstate.cgc[i]*sstate.cgc[i];
+                            vv = vv+sstate.cgp[i]*sstate.cgp[i];
+                            b = b || ((sstate.havebndl[i] && (double)(sstate.sas.xc[i])==(double)(sstate.bndl[i])) && (double)(sstate.dp[i])!=(double)(0));
+                            b = b || ((sstate.havebndu[i] && (double)(sstate.sas.xc[i])==(double)(sstate.bndu[i])) && (double)(sstate.dp[i])!=(double)(0));
+                        }
+                        b = b || (double)(vv)==(double)(0);
+                        b = b || cgcnt%quickqprestartcg==0;
+                        if( !b )
+                        {
+                            beta = v/vv;
+                        }
+                        else
+                        {
+                            beta = 0.0;
+                        }
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            sstate.dc[i_] = sstate.dc[i_] + beta*sstate.dp[i_];
+                        }
+                        sactivesets.sasconstraineddirection(sstate.sas, ref sstate.dc, _params);
+                        sactivesets.sasexploredirection(sstate.sas, sstate.dc, ref stpmax, ref cidx, ref cval, _params);
+                        
+                        //
+                        // Build quadratic model of F along descent direction:
+                        //
+                        //     F(xc+alpha*D) = D2*alpha^2 + D1*alpha
+                        //
+                        // Terminate algorithm if needed.
+                        //
+                        // NOTE: we do not maintain constant term D0
+                        //
+                        quadraticmodel(sstate, sstate.sas.xc, sstate.dc, sstate.gc, ref d1, ref d1est, ref d2, ref d2est, sstate.tmp0, _params);
+                        if( (double)(d1)==(double)(0) && (double)(d2)==(double)(0) )
+                        {
+                            
+                            //
+                            // D1 and D2 are exactly zero, success.
+                            // After this if-then we assume that D is non-zero.
+                            //
+                            terminationtype = 4;
+                            break;
+                        }
+                        if( d1est>=0 )
+                        {
+                            
+                            //
+                            // Numerical noise is too large, it means that we are close
+                            // to minimum - and that further improvement is impossible.
+                            //
+                            // After this if-then we assume that D1 is definitely negative
+                            // (even under presence of numerical errors).
+                            //
+                            terminationtype = 7;
+                            break;
+                        }
+                        if( d2est<=0 && cidx<0 )
+                        {
+                            
+                            //
+                            // Function is unbounded from below:
+                            // * D1<0 (verified by previous block)
+                            // * D2Est<=0, which means that either D2<0 - or it can not
+                            //   be reliably distinguished from zero.
+                            // * step is unconstrained
+                            //
+                            // If these conditions are true, we abnormally terminate QP
+                            // algorithm with return code -4
+                            //
+                            terminationtype = -4;
+                            break;
+                        }
+                        
+                        //
+                        // Perform step along DC.
+                        //
+                        // In this block of code we maintain two step length:
+                        // * RestStp -  restricted step, maximum step length along DC which does
+                        //              not violate constraints
+                        // * FullStp -  step length along DC which minimizes quadratic function
+                        //              without taking constraints into account. If problem is
+                        //              unbounded from below without constraints, FullStp is
+                        //              forced to be RestStp.
+                        //
+                        // So, if function is convex (D2>0):
+                        // * FullStp = -D1/(2*D2)
+                        // * RestStp = restricted FullStp
+                        // * 0<=RestStp<=FullStp
+                        //
+                        // If function is non-convex, but bounded from below under constraints:
+                        // * RestStp = step length subject to constraints
+                        // * FullStp = RestStp
+                        //
+                        // After RestStp and FullStp are initialized, we generate several trial
+                        // steps which are different multiples of RestStp and FullStp.
+                        //
+                        if( d2est>0 )
+                        {
+                            alglib.ap.assert((double)(d1)<(double)(0), "QQPOptimize: internal error");
+                            fullstp = -(d1/(2*d2));
+                            needact = (double)(fullstp)>=(double)(stpmax);
+                            if( needact )
+                            {
+                                alglib.ap.assert(alglib.ap.len(sstate.stpbuf)>=3, "QQPOptimize: StpBuf overflow");
+                                reststp = stpmax;
+                                stp = reststp;
+                                sstate.stpbuf[0] = reststp*4;
+                                sstate.stpbuf[1] = fullstp;
+                                sstate.stpbuf[2] = fullstp/4;
+                                stpcnt = 3;
+                            }
+                            else
+                            {
+                                reststp = fullstp;
+                                stp = fullstp;
+                                stpcnt = 0;
+                            }
+                        }
+                        else
+                        {
+                            alglib.ap.assert(cidx>=0, "QQPOptimize: internal error");
+                            alglib.ap.assert(alglib.ap.len(sstate.stpbuf)>=2, "QQPOptimize: StpBuf overflow");
+                            reststp = stpmax;
+                            fullstp = stpmax;
+                            stp = reststp;
+                            needact = true;
+                            sstate.stpbuf[0] = 4*reststp;
+                            stpcnt = 1;
+                        }
+                        findbeststepandmove(sstate, sstate.sas, sstate.dc, stp, needact, cidx, cval, sstate.stpbuf, stpcnt, ref sstate.activated, ref sstate.tmp0, ref sstate.tmp1, _params);
+                        
+                        //
+                        // Update CG information.
+                        //
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            sstate.dp[i_] = sstate.dc[i_];
+                        }
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            sstate.cgp[i_] = sstate.cgc[i_];
+                        }
+                        
+                        //
+                        // Update iterations counter
+                        //
+                        sstate.repinneriterationscount = sstate.repinneriterationscount+1;
+                    }
+                    if( terminationtype!=0 )
+                    {
+                        break;
+                    }
+                    cgmax = settings.cgmaxits;
+                    
+                    //
+                    // Generate YIdx - reordering of variables for constrained Newton phase.
+                    // Free variables come first, fixed are last ones.
+                    //
+                    newtcnt = 0;
+                    while( true )
+                    {
+                        
+                        //
+                        // Skip iteration if constrained Newton is turned off.
+                        //
+                        if( !settings.cnphase )
+                        {
+                            break;
+                        }
+                        
+                        //
+                        // At the first iteration   - build Cholesky decomposition of Hessian.
+                        // At subsequent iterations - refine Hessian by adding new constraints.
+                        //
+                        // Loop is terminated in following cases:
+                        // * Hessian is not positive definite subject to current constraints
+                        //   (termination during initial decomposition)
+                        // * there were no new constraints being activated
+                        //   (termination during update)
+                        // * all constraints were activated during last step
+                        //   (termination during update)
+                        // * CNMaxUpdates were performed on matrix
+                        //   (termination during update)
+                        //
+                        if( newtcnt==0 )
+                        {
+                            
+                            //
+                            // Perform initial Newton step. If Cholesky decomposition fails,
+                            // increase number of CG iterations to CGMaxIts - it should help
+                            // us to find set of constraints which will make matrix positive
+                            // definite.
+                            //
+                            b = cnewtonbuild(sstate, sparsesolver, ref sstate.repncholesky, _params);
+                            if( b )
+                            {
+                                cgmax = settings.cgminits;
+                            }
+                        }
+                        else
+                        {
+                            b = cnewtonupdate(sstate, settings, ref sstate.repncupdates, _params);
+                        }
+                        if( !b )
+                        {
+                            break;
+                        }
+                        apserv.inc(ref newtcnt, _params);
+                        
+                        //
+                        // Calculate gradient GC.
+                        //
+                        targetgradient(sstate, sstate.sas.xc, ref sstate.gc, _params);
+                        
+                        //
+                        // Bound-constrained Newton step
+                        //
+                        for(i=0; i<=n-1; i++)
+                        {
+                            sstate.dc[i] = sstate.gc[i];
+                        }
+                        if( !cnewtonstep(sstate, settings, sstate.dc, _params) )
+                        {
+                            break;
+                        }
+                        quadraticmodel(sstate, sstate.sas.xc, sstate.dc, sstate.gc, ref d1, ref d1est, ref d2, ref d2est, sstate.tmp0, _params);
+                        if( d1est>=0 )
+                        {
+                            
+                            //
+                            // We are close to minimum, derivative is nearly zero, break Newton iteration
+                            //
+                            break;
+                        }
+                        if( d2est>0 )
+                        {
+                            
+                            //
+                            // Positive definite matrix, we can perform Newton step
+                            //
+                            alglib.ap.assert((double)(d1)<(double)(0), "QQPOptimize: internal error");
+                            fullstp = -(d1/(2*d2));
+                            sactivesets.sasexploredirection(sstate.sas, sstate.dc, ref stpmax, ref cidx, ref cval, _params);
+                            needact = (double)(fullstp)>=(double)(stpmax);
+                            if( needact )
+                            {
+                                alglib.ap.assert(alglib.ap.len(sstate.stpbuf)>=3, "QQPOptimize: StpBuf overflow");
+                                reststp = stpmax;
+                                stp = reststp;
+                                sstate.stpbuf[0] = reststp*4;
+                                sstate.stpbuf[1] = fullstp;
+                                sstate.stpbuf[2] = fullstp/4;
+                                stpcnt = 3;
+                            }
+                            else
+                            {
+                                reststp = fullstp;
+                                stp = fullstp;
+                                stpcnt = 0;
+                            }
+                            findbeststepandmove(sstate, sstate.sas, sstate.dc, stp, needact, cidx, cval, sstate.stpbuf, stpcnt, ref sstate.activated, ref sstate.tmp0, ref sstate.tmp1, _params);
+                        }
+                        else
+                        {
+                            
+                            //
+                            // Matrix is semi-definite or indefinite, but regularized
+                            // Cholesky succeeded and gave us descent direction in DC.
+                            //
+                            // We will investigate it and try to perform descent step:
+                            // * first, we explore direction:
+                            //   * if it is unbounded, we stop algorithm with
+                            //     appropriate termination code -4.
+                            //   * if StpMax=0, we break Newton phase and return to
+                            //     CG phase - constraint geometry is complicated near
+                            //     current point, so it is better to use simpler algo.
+                            // * second, we check that bounded step decreases function;
+                            //   if not, we again skip to CG phase
+                            // * finally, we use FindBestStep...() function to choose
+                            //   between bounded step and projection of full-length step
+                            //   (latter may give additional decrease in
+                            //
+                            sactivesets.sasexploredirection(sstate.sas, sstate.dc, ref stpmax, ref cidx, ref cval, _params);
+                            if( cidx<0 )
+                            {
+                                
+                                //
+                                // Function is unbounded from below:
+                                // * D1<0 (verified by previous block)
+                                // * D2Est<=0, which means that either D2<0 - or it can not
+                                //   be reliably distinguished from zero.
+                                // * step is unconstrained
+                                //
+                                // If these conditions are true, we abnormally terminate QP
+                                // algorithm with return code -4
+                                //
+                                terminationtype = -4;
+                                break;
+                            }
+                            if( (double)(stpmax)==(double)(0) )
+                            {
+                                
+                                //
+                                // Resort to CG phase.
+                                // Increase number of CG iterations.
+                                //
+                                cgmax = settings.cgmaxits;
+                                break;
+                            }
+                            alglib.ap.assert((double)(stpmax)>(double)(0), "QQPOptimize: internal error");
+                            f0 = projectedtargetfunction(sstate, sstate.sas.xc, sstate.dc, 0.0, ref sstate.tmp0, ref sstate.tmp1, _params);
+                            f1 = projectedtargetfunction(sstate, sstate.sas.xc, sstate.dc, stpmax, ref sstate.tmp0, ref sstate.tmp1, _params);
+                            if( (double)(f1)>=(double)(f0) )
+                            {
+                                
+                                //
+                                // Descent direction does not actually decrease function value.
+                                // Resort to CG phase
+                                // Increase number of CG iterations.
+                                //
+                                cgmax = settings.cgmaxits;
+                                break;
+                            }
+                            alglib.ap.assert(alglib.ap.len(sstate.stpbuf)>=3, "QQPOptimize: StpBuf overflow");
+                            reststp = stpmax;
+                            stp = reststp;
+                            sstate.stpbuf[0] = reststp*4;
+                            sstate.stpbuf[1] = 1.00;
+                            sstate.stpbuf[2] = 0.25;
+                            stpcnt = 3;
+                            findbeststepandmove(sstate, sstate.sas, sstate.dc, stp, true, cidx, cval, sstate.stpbuf, stpcnt, ref sstate.activated, ref sstate.tmp0, ref sstate.tmp1, _params);
+                        }
+                    }
+                    if( terminationtype!=0 )
+                    {
+                        break;
+                    }
+                }
+                sactivesets.sasstopoptimization(sstate.sas, _params);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    sstate.xf[i_] = sstate.sas.xc[i_];
+                }
+            }
+            
+            //
+            // Stop optimization and unpack results.
+            //
+            // Add XOriginC to XS and make sure that boundary constraints are
+            // both (a) satisfied, (b) preserved. Former means that "shifted"
+            // point is feasible, while latter means that point which was exactly
+            // at the boundary before shift will be exactly at the boundary
+            // after shift.
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                xs[i] = sc[i]*sstate.xf[i]+xoriginc[i];
+                if( sstate.havebndl[i] && (double)(xs[i])<(double)(bndlc[i]) )
+                {
+                    xs[i] = bndlc[i];
+                }
+                if( sstate.havebndu[i] && (double)(xs[i])>(double)(bnduc[i]) )
+                {
+                    xs[i] = bnduc[i];
+                }
+                if( sstate.havebndl[i] && (double)(sstate.xf[i])==(double)(sstate.bndl[i]) )
+                {
+                    xs[i] = bndlc[i];
+                }
+                if( sstate.havebndu[i] && (double)(sstate.xf[i])==(double)(sstate.bndu[i]) )
+                {
+                    xs[i] = bnduc[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Target function at point PROJ(X+Stp*D), where PROJ(.) is a projection into
+        feasible set.
+
+        NOTE: if Stp=0, D is not referenced at all. Thus,  there  is  no  need  to
+              fill it by some meaningful values for Stp=0.
+
+        This subroutine uses temporary buffers  Tmp0/1,  which  are  automatically
+        resized if needed.
+
+          -- ALGLIB --
+             Copyright 21.12.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static double projectedtargetfunction(qqpbuffers sstate,
+            double[] x,
+            double[] d,
+            double stp,
+            ref double[] tmp0,
+            ref double[] tmp1,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int n = 0;
+            int i = 0;
+            double v = 0;
+
+            n = sstate.n;
+            apserv.rvectorsetlengthatleast(ref tmp0, n, _params);
+            apserv.rvectorsetlengthatleast(ref tmp1, n, _params);
+            
+            //
+            // Calculate projected point
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(stp)!=(double)(0) )
+                {
+                    v = x[i]+stp*d[i];
+                }
+                else
+                {
+                    v = x[i];
+                }
+                if( sstate.havebndl[i] && (double)(v)<(double)(sstate.bndl[i]) )
+                {
+                    v = sstate.bndl[i];
+                }
+                if( sstate.havebndu[i] && (double)(v)>(double)(sstate.bndu[i]) )
+                {
+                    v = sstate.bndu[i];
+                }
+                tmp0[i] = v;
+            }
+            
+            //
+            // Function value at the Tmp0:
+            //
+            // f(x) = 0.5*x'*A*x + b'*x
+            //
+            result = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                result = result+sstate.b[i]*tmp0[i];
+            }
+            if( sstate.akind==0 )
+            {
+                
+                //
+                // Dense matrix A
+                //
+                result = result+0.5*ablas.rmatrixsyvmv(n, sstate.densea, 0, 0, true, tmp0, 0, tmp1, _params);
+            }
+            else
+            {
+                
+                //
+                // sparse matrix A
+                //
+                alglib.ap.assert(sstate.akind==1, "QQPOptimize: unexpected AKind in ProjectedTargetFunction");
+                result = result+0.5*sparse.sparsevsmv(sstate.sparsea, sstate.sparseupper, tmp0, _params);
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Gradient of the target function:
+
+            f(x) = 0.5*x'*A*x + b'*x
+            
+        which is equal to
+
+            grad = A*x + b
+
+        Here:
+        * x is array[N]
+        * A is array[N,N]
+        * b is array[N]
+
+        INPUT PARAMETERS:
+            SState  -   structure which stores function terms (not modified)
+            X       -   location
+            G       -   possibly preallocated buffer
+
+        OUTPUT PARAMETERS:
+            G       -   array[N], gradient
+                    
+          -- ALGLIB --
+             Copyright 21.12.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void targetgradient(qqpbuffers sstate,
+            double[] x,
+            ref double[] g,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i_ = 0;
+
+            n = sstate.n;
+            apserv.rvectorsetlengthatleast(ref g, n, _params);
+            if( sstate.akind==0 )
+            {
+                
+                //
+                // Dense matrix A
+                //
+                ablas.rmatrixsymv(n, 1.0, sstate.densea, 0, 0, true, x, 0, 0.0, g, 0, _params);
+            }
+            else
+            {
+                
+                //
+                // Sparse matrix A
+                //
+                alglib.ap.assert(sstate.akind==1, "QQPOptimize: unexpected AKind in TargetGradient");
+                sparse.sparsesmv(sstate.sparsea, sstate.sparseupper, x, ref g, _params);
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                g[i_] = g[i_] + sstate.b[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        First and second derivatives  of  the  "extended"  target  function  along
+        specified direction. Target  function  is  called  "extended"  because  of
+        additional slack variables and has form:
+
+            f(x) = 0.5*x'*A*x + b'*x + penaltyfactor*0.5*(C*x-b)'*(C*x-b)
+            
+        with gradient
+
+            grad = A*x + b + penaltyfactor*C'*(C*x-b)
+            
+        Quadratic model has form
+
+            F(x0+alpha*D) = D2*alpha^2 + D1*alpha
+
+        INPUT PARAMETERS:
+            SState  -   structure which is used to obtain quadratic term of the model
+            X       -   current point, array[N]
+            D       -   direction across which derivatives are calculated, array[N]
+            G       -   gradient at current point (pre-calculated by caller), array[N]
+
+        OUTPUT PARAMETERS:
+            D1      -   linear coefficient
+            D1Est   -   estimate of D1 sign,  accounting  for  possible  numerical
+                        errors:
+                        * >0    means "almost surely positive"
+                        * <0    means "almost surely negative"
+                        * =0    means "pessimistic estimate  of  numerical  errors
+                                in D1 is larger than magnitude of D1 itself; it is
+                                impossible to reliably distinguish D1 from zero".
+            D2      -   quadratic coefficient
+            D2Est   -   estimate of D2 sign,  accounting  for  possible  numerical
+                        errors:
+                        * >0    means "almost surely positive"
+                        * <0    means "almost surely negative"
+                        * =0    means "pessimistic estimate  of  numerical  errors
+                                in D2 is larger than magnitude of D2 itself; it is
+                                impossible to reliably distinguish D2 from zero".
+                    
+          -- ALGLIB --
+             Copyright 14.05.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static void quadraticmodel(qqpbuffers sstate,
+            double[] x,
+            double[] d,
+            double[] g,
+            ref double d1,
+            ref int d1est,
+            ref double d2,
+            ref int d2est,
+            double[] tmp0,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            double v = 0;
+            double mx = 0;
+            double mb = 0;
+            double md = 0;
+            int i_ = 0;
+
+            d1 = 0;
+            d1est = 0;
+            d2 = 0;
+            d2est = 0;
+
+            n = sstate.n;
+            
+            //
+            // Maximums
+            //
+            mx = 0.0;
+            md = 0.0;
+            mb = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                mx = Math.Max(mx, Math.Abs(x[i]));
+                md = Math.Max(md, Math.Abs(d[i]));
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                mb = Math.Max(mb, Math.Abs(sstate.b[i]));
+            }
+            
+            //
+            // D2
+            //
+            if( sstate.akind==0 )
+            {
+                
+                //
+                // Dense matrix A
+                //
+                d2 = 0.5*ablas.rmatrixsyvmv(n, sstate.densea, 0, 0, true, d, 0, tmp0, _params);
+            }
+            else
+            {
+                
+                //
+                // Sparse matrix A
+                //
+                alglib.ap.assert(sstate.akind==1, "QQPOptimize: unexpected AKind in TargetGradient");
+                d2 = 0.5*sparse.sparsevsmv(sstate.sparsea, sstate.sparseupper, d, _params);
+            }
+            v = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                v += d[i_]*g[i_];
+            }
+            d1 = v;
+            
+            //
+            // Error estimates
+            //
+            optserv.estimateparabolicmodel(sstate.absasum, sstate.absasum2, mx, mb, md, d1, d2, ref d1est, ref d2est, _params);
+        }
+
+
+        /*************************************************************************
+        This function accepts quadratic model of the form
+
+            f(x) = 0.5*x'*A*x + b'*x + penaltyfactor*0.5*(C*x-b)'*(C*x-b)
+            
+        and list of possible steps along direction D. It chooses  best  step  (one
+        which achieves minimum value of the target  function)  and  moves  current
+        point (given by SAS object) to the new location. Step is  bounded  subject
+        to boundary constraints.
+
+        Candidate steps are divided into two groups:
+        * "default" step, which is always performed when no candidate steps LONGER
+          THAN THE DEFAULT  ONE  is  given.  This  candidate  MUST  reduce  target
+          function value; it is  responsibility  of  caller  to   provide  default
+          candidate which reduces target function.
+        * "additional candidates", which may be shorter or longer than the default
+          step. Candidates which are shorter that the default  step  are  ignored;
+          candidates which are longer than the "default" step are tested.
+          
+        The idea is that we ALWAYS try "default" step, and it is responsibility of
+        the caller to provide us with something which is worth trying.  This  step
+        may activate some constraint - that's why we  stopped  at  "default"  step
+        size. However, we may also try longer steps which may activate  additional
+        constraints and further reduce function value.
+
+        INPUT PARAMETERS:
+            SState  -   structure which stores model
+            SAS     -   active set structure which stores current point in SAS.XC
+            D       -   direction for step
+            Stp     -   step length for "default" candidate
+            NeedAct -   whether   default  candidate  activates  some  constraint;
+                        if NeedAct  is True,  constraint  given  by  CIdc/CVal  is
+                        GUARANTEED to be activated in the final point.
+            CIdx    -   if NeedAct is True, stores index of the constraint to activate
+            CVal    -   if NeedAct is True, stores constrained value;
+                        SAS.XC[CIdx] is forced to be equal to CVal.
+            AddSteps-   array[AddStepsCnt] of additional steps:
+                        * AddSteps[]<=Stp are ignored
+                        * AddSteps[]>Stp are tried
+            Activated-  possibly preallocated buffer; previously allocated memory
+                        will be reused.
+            Tmp0/1   -  possibly preallocated buffers; previously allocated memory
+                        will be reused.
+            
+        OUTPUT PARAMETERS:
+            SAS     -   SAS.XC is set to new point;  if  there  was  a  constraint
+                        specified  by  NeedAct/CIdx/CVal,  it  will  be  activated
+                        (other constraints may be activated too, but this  one  is
+                        guaranteed to be active in the final point).
+            Activated-  elements of this array are set to True, if I-th constraint
+                        as inactive at previous point, but become  active  in  the
+                        new one.
+                        Situations when we deactivate xi>=0 and activate xi<=1 are
+                        considered as activation of previously inactive constraint
+                    
+          -- ALGLIB --
+             Copyright 14.05.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static void findbeststepandmove(qqpbuffers sstate,
+            sactivesets.sactiveset sas,
+            double[] d,
+            double stp,
+            bool needact,
+            int cidx,
+            double cval,
+            double[] addsteps,
+            int addstepscnt,
+            ref bool[] activated,
+            ref double[] tmp0,
+            ref double[] tmp1,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int k = 0;
+            double v = 0;
+            double stpbest = 0;
+            double fbest = 0;
+            double fcand = 0;
+
+            n = sstate.n;
+            apserv.rvectorsetlengthatleast(ref tmp0, n, _params);
+            apserv.bvectorsetlengthatleast(ref activated, n, _params);
+            
+            //
+            // Calculate initial step, store to Tmp0
+            //
+            // NOTE: Tmp0 is guaranteed to be feasible w.r.t. boundary constraints
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                v = sas.xc[i]+stp*d[i];
+                if( sstate.havebndl[i] && (double)(v)<(double)(sstate.bndl[i]) )
+                {
+                    v = sstate.bndl[i];
+                }
+                if( sstate.havebndu[i] && (double)(v)>(double)(sstate.bndu[i]) )
+                {
+                    v = sstate.bndu[i];
+                }
+                tmp0[i] = v;
+            }
+            if( needact )
+            {
+                tmp0[cidx] = cval;
+            }
+            
+            //
+            // Try additional steps, if AddStepsCnt>0
+            //
+            if( addstepscnt>0 )
+            {
+                
+                //
+                // Find best step
+                //
+                stpbest = stp;
+                fbest = projectedtargetfunction(sstate, sas.xc, d, stpbest, ref tmp0, ref tmp1, _params);
+                for(k=0; k<=addstepscnt-1; k++)
+                {
+                    if( (double)(addsteps[k])>(double)(stp) )
+                    {
+                        fcand = projectedtargetfunction(sstate, sas.xc, d, addsteps[k], ref tmp0, ref tmp1, _params);
+                        if( (double)(fcand)<(double)(fbest) )
+                        {
+                            fbest = fcand;
+                            stpbest = addsteps[k];
+                        }
+                    }
+                }
+                
+                //
+                // Prepare best step
+                //
+                // NOTE: because only AddSteps[]>Stp were checked,
+                //       this step will activate constraint CIdx.
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    v = sas.xc[i]+stpbest*d[i];
+                    if( sstate.havebndl[i] && (double)(v)<(double)(sstate.bndl[i]) )
+                    {
+                        v = sstate.bndl[i];
+                    }
+                    if( sstate.havebndu[i] && (double)(v)>(double)(sstate.bndu[i]) )
+                    {
+                        v = sstate.bndu[i];
+                    }
+                    tmp0[i] = v;
+                }
+                if( needact )
+                {
+                    tmp0[cidx] = cval;
+                }
+            }
+            
+            //
+            // Fill Activated array by information about activated constraints.
+            // Perform step
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                activated[i] = false;
+                v = tmp0[i];
+                if( (double)(v)==(double)(sas.xc[i]) )
+                {
+                    continue;
+                }
+                if( sstate.havebndl[i] && (double)(v)==(double)(sstate.bndl[i]) )
+                {
+                    activated[i] = true;
+                }
+                if( sstate.havebndu[i] && (double)(v)==(double)(sstate.bndu[i]) )
+                {
+                    activated[i] = true;
+                }
+            }
+            sactivesets.sasmoveto(sas, tmp0, needact, cidx, cval, _params);
+        }
+
+
+        /*************************************************************************
+        This function prepares data for  constrained  Newton  step  for  penalized
+        quadratic model of the form
+
+            f(x) = 0.5*x'*A*x + b'*x + penaltyfactor*0.5*(C*x-b)'*(C*x-b)
+            
+        where A can be dense or sparse, and model is considered subject to equality
+        constraints specified by SState.SAS.XC  object.  Constraint  is considered
+        active if XC[i] is exactly BndL[i] or BndU[i],  i.e.  we  ignore  internal
+        list of constraints monitored by SAS object. Our own  set  of  constraints
+        includes all  constraints  stored  by  SAS,  but  also  may  include  some
+        constraints which are inactive in SAS.
+
+        "Preparation" means that Cholesky decomposition of  the  effective  system
+        matrix is performed, and we can  perform  constrained  Newton  step.
+
+        This function works as black box. It uses fields of SState which are marked
+        as "Variables for constrained Newton phase", and only  this  function  and
+        its friends know about these variables. Everyone else should use:
+        * CNewtonBuild() to prepare initial Cholesky decomposition for step
+        * CNewtonStep() to perform constrained Newton step
+        * CNewtonUpdate() to update Cholesky matrix  after  point  was  moved  and
+          constraints were updated. In some cases it  is  possible to  efficiently
+          re-calculate Cholesky decomposition if you know which  constraints  were
+          activated. If efficient  re-calculation  is  impossible,  this  function
+          returns False.
+
+        INPUT PARAMETERS:
+            SState  -   structure which stores model and temporaries for CN phase;
+                        in particular, SAS.XC stores current point.
+            SparseSolver-which sparse solver to use for sparse model; ignored  for
+                        dense QP. Can be:
+                        * 2 -   SKS-based Cholesky
+            NCholesky-  counter which is incremented after Cholesky (successful or
+                        failed one)
+            
+        OUTPUT PARAMETERS:
+            NCholesky-  possibly updated counter
+            
+        RESULT:
+            True, if Cholesky decomposition was successfully performed.
+            False, if a) matrix was semi-definite or indefinite, or b)  particular
+            combination of matrix type (sparse) and constraints  (general  linear)
+            is not supported.
+            
+        NOTE: this function may routinely return False, for indefinite matrices or
+              for sparse problems with general linear constraints. You  should  be
+              able to handle such situations.
+                    
+          -- ALGLIB --
+             Copyright 14.05.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool cnewtonbuild(qqpbuffers sstate,
+            int sparsesolver,
+            ref int ncholesky,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            bool b = new bool();
+            int ridx0 = 0;
+            int ridx1 = 0;
+            int nfree = 0;
+            int i_ = 0;
+
+            result = false;
+            
+            //
+            // Fetch often used fields
+            //
+            n = sstate.n;
+            
+            //
+            // 1. Set CNModelAge to zero
+            // 2. Generate YIdx - reordering of variables such that free variables
+            //    come first and are ordered by ascending, fixed are last ones and
+            //    have no particular ordering.
+            //
+            // This step is same for dense and sparse problems.
+            //
+            sstate.cnmodelage = 0;
+            apserv.ivectorsetlengthatleast(ref sstate.yidx, n, _params);
+            ridx0 = 0;
+            ridx1 = n-1;
+            for(i=0; i<=n-1; i++)
+            {
+                sstate.yidx[i] = -1;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(!sstate.havebndl[i] || (double)(sstate.sas.xc[i])>=(double)(sstate.bndl[i]), "CNewtonBuild: internal error");
+                alglib.ap.assert(!sstate.havebndu[i] || (double)(sstate.sas.xc[i])<=(double)(sstate.bndu[i]), "CNewtonBuild: internal error");
+                b = false;
+                b = b || (sstate.havebndl[i] && (double)(sstate.sas.xc[i])==(double)(sstate.bndl[i]));
+                b = b || (sstate.havebndu[i] && (double)(sstate.sas.xc[i])==(double)(sstate.bndu[i]));
+                if( b )
+                {
+                    sstate.yidx[ridx1] = i;
+                    ridx1 = ridx1-1;
+                }
+                else
+                {
+                    sstate.yidx[ridx0] = i;
+                    ridx0 = ridx0+1;
+                }
+            }
+            alglib.ap.assert(ridx0==ridx1+1, "CNewtonBuild: internal error");
+            nfree = ridx0;
+            sstate.nfree = nfree;
+            if( nfree==0 )
+            {
+                return result;
+            }
+            
+            //
+            // Constrained Newton matrix: dense version
+            //
+            if( sstate.akind==0 )
+            {
+                apserv.rmatrixsetlengthatleast(ref sstate.densez, n, n, _params);
+                apserv.rvectorsetlengthatleast(ref sstate.tmpcn, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=i; j<=n-1; j++)
+                    {
+                        sstate.densez[i,j] = sstate.densea[i,j];
+                    }
+                }
+                for(i=1; i<=nfree-1; i++)
+                {
+                    alglib.ap.assert(sstate.yidx[i]>sstate.yidx[i-1], "CNewtonBuild: integrity check failed");
+                }
+                for(i=0; i<=nfree-1; i++)
+                {
+                    k = sstate.yidx[i];
+                    for(j=i; j<=nfree-1; j++)
+                    {
+                        sstate.densez[i,j] = sstate.densez[k,sstate.yidx[j]];
+                    }
+                }
+                apserv.rvectorsetlengthatleast(ref sstate.regdiag, n, _params);
+                for(i=0; i<=nfree-1; i++)
+                {
+                    v = 0.0;
+                    for(j=0; j<=i-1; j++)
+                    {
+                        v = v+Math.Abs(sstate.densez[j,i]);
+                    }
+                    for(j=i; j<=nfree-1; j++)
+                    {
+                        v = v+Math.Abs(sstate.densez[i,j]);
+                    }
+                    if( (double)(v)==(double)(0) )
+                    {
+                        v = 1.0;
+                    }
+                    sstate.regdiag[i] = regz*v;
+                }
+                for(i=0; i<=nfree-1; i++)
+                {
+                    sstate.densez[i,i] = sstate.densez[i,i]+sstate.regdiag[i];
+                }
+                apserv.inc(ref ncholesky, _params);
+                if( !trfac.spdmatrixcholeskyrec(ref sstate.densez, 0, nfree, true, ref sstate.tmpcn, _params) )
+                {
+                    return result;
+                }
+                for(i=nfree-1; i>=0; i--)
+                {
+                    for(i_=i; i_<=nfree-1;i_++)
+                    {
+                        sstate.tmpcn[i_] = sstate.densez[i,i_];
+                    }
+                    k = sstate.yidx[i];
+                    for(j=k; j<=n-1; j++)
+                    {
+                        sstate.densez[k,j] = 0;
+                    }
+                    for(j=i; j<=nfree-1; j++)
+                    {
+                        sstate.densez[k,sstate.yidx[j]] = sstate.tmpcn[j];
+                    }
+                }
+                for(i=nfree; i<=n-1; i++)
+                {
+                    k = sstate.yidx[i];
+                    sstate.densez[k,k] = 1.0;
+                    for(j=k+1; j<=n-1; j++)
+                    {
+                        sstate.densez[k,j] = 0;
+                    }
+                }
+                result = true;
+                return result;
+            }
+            
+            //
+            // Constrained Newton matrix: sparse version
+            //
+            if( sstate.akind==1 )
+            {
+                alglib.ap.assert(sparsesolver==2, "CNewtonBuild: internal error");
+                
+                //
+                // Copy sparse A to Z and fill rows/columns corresponding to active
+                // constraints by zeros. Diagonal elements corresponding to active
+                // constraints are filled by unit values.
+                //
+                sparse.sparsecopytosksbuf(sstate.sparsea, sstate.sparsecca, _params);
+                apserv.rvectorsetlengthatleast(ref sstate.tmpcn, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    sstate.tmpcn[i] = 0;
+                }
+                for(i=nfree; i<=n-1; i++)
+                {
+                    sstate.tmpcn[sstate.yidx[i]] = 1;
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    k = sstate.sparsecca.ridx[i];
+                    for(j=i-sstate.sparsecca.didx[i]; j<=i; j++)
+                    {
+                        if( (double)(sstate.tmpcn[i])!=(double)(0) || (double)(sstate.tmpcn[j])!=(double)(0) )
+                        {
+                            
+                            //
+                            // I-th or J-th variable is in active set (constrained)
+                            //
+                            if( i==j )
+                            {
+                                sstate.sparsecca.vals[k] = 1.0;
+                            }
+                            else
+                            {
+                                sstate.sparsecca.vals[k] = 0.0;
+                            }
+                        }
+                        k = k+1;
+                    }
+                }
+                
+                //
+                // Perform sparse Cholesky
+                //
+                apserv.inc(ref ncholesky, _params);
+                if( !trfac.sparsecholeskyskyline(sstate.sparsecca, n, sstate.sparseupper, _params) )
+                {
+                    return result;
+                }
+                result = true;
+                return result;
+            }
+            
+            //
+            // Unexpected :)
+            //
+            alglib.ap.assert(false, "CNewtonBuild: internal error");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This   function  updates  equality-constrained   Cholesky   matrix   after
+        activation of the  new  equality  constraints.  Matrix  being  updated  is
+        quadratic term of the function below
+
+            f(x) = 0.5*x'*A*x + b'*x + penaltyfactor*0.5*(C*x-b)'*(C*x-b)
+            
+        where A can be dense or sparse.
+
+        This  function  uses  YIdx[]  array  (set by CNewtonBuild()  function)  to
+        distinguish between active and inactive constraints.
+
+        This function works as black box. It uses fields of SState which are marked
+        as "Variables for constrained Newton phase", and only  this  function  and
+        its friends know about these variables. Everyone else should use:
+        * CNewtonBuild() to prepare initial Cholesky decomposition for step
+        * CNewtonStep() to perform constrained Newton step
+        * CNewtonUpdate() to update Cholesky matrix  after  point  was  moved  and
+          constraints were updated. In some cases it  is  possible to  efficiently
+          re-calculate Cholesky decomposition if you know which  constraints  were
+          activated. If efficient  re-calculation  is  impossible,  this  function
+          returns False.
+
+        INPUT PARAMETERS:
+            SState  -   structure which stores model and temporaries for CN phase;
+                        in particular, SAS.XC stores current point.
+            Settings -  QQPSettings object which was  initialized  by  appropriate
+                        construction function.
+            NCUpdates-  counter which is incremented after each update (one update
+                        means one variable being fixed)
+            
+        OUTPUT PARAMETERS:
+            NCUpdates-  possibly updated counter
+            
+        RESULT:
+            True, if Cholesky decomposition was successfully performed.
+            False, if a) model age was too high, or b) particular  combination  of
+            matrix type (sparse) and constraints (general linear) is not supported
+            
+        NOTE: this function may routinely return False.
+              You should be able to handle such situations.
+                    
+          -- ALGLIB --
+             Copyright 14.05.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool cnewtonupdate(qqpbuffers sstate,
+            qqpsettings settings,
+            ref int ncupdates,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int nfree = 0;
+            int ntofix = 0;
+            bool b = new bool();
+            int ridx0 = 0;
+            int ridx1 = 0;
+            int i = 0;
+            int k = 0;
+
+            result = false;
+            
+            //
+            // Cholesky updates for sparse problems are not supported
+            //
+            if( sstate.akind==1 )
+            {
+                return result;
+            }
+            
+            //
+            // Fetch often used fields
+            //
+            n = sstate.n;
+            nfree = sstate.nfree;
+            
+            //
+            // Determine variables to fix and move them to YIdx[NFree-NToFix:NFree-1]
+            // Exit if CNModelAge increased too much.
+            //
+            apserv.ivectorsetlengthatleast(ref sstate.tmpcni, n, _params);
+            ridx0 = 0;
+            ridx1 = nfree-1;
+            for(i=0; i<=nfree-1; i++)
+            {
+                sstate.tmpcni[i] = -1;
+            }
+            for(k=0; k<=nfree-1; k++)
+            {
+                i = sstate.yidx[k];
+                alglib.ap.assert(!sstate.havebndl[i] || (double)(sstate.sas.xc[i])>=(double)(sstate.bndl[i]), "CNewtonUpdate: internal error");
+                alglib.ap.assert(!sstate.havebndu[i] || (double)(sstate.sas.xc[i])<=(double)(sstate.bndu[i]), "CNewtonUpdate: internal error");
+                b = false;
+                b = b || (sstate.havebndl[i] && (double)(sstate.sas.xc[i])==(double)(sstate.bndl[i]));
+                b = b || (sstate.havebndu[i] && (double)(sstate.sas.xc[i])==(double)(sstate.bndu[i]));
+                if( b )
+                {
+                    sstate.tmpcni[ridx1] = i;
+                    ridx1 = ridx1-1;
+                }
+                else
+                {
+                    sstate.tmpcni[ridx0] = i;
+                    ridx0 = ridx0+1;
+                }
+            }
+            alglib.ap.assert(ridx0==ridx1+1, "CNewtonUpdate: internal error");
+            ntofix = nfree-ridx0;
+            if( ntofix==0 || ntofix==nfree )
+            {
+                return result;
+            }
+            if( sstate.cnmodelage+ntofix>settings.cnmaxupdates )
+            {
+                return result;
+            }
+            for(i=0; i<=nfree-1; i++)
+            {
+                sstate.yidx[i] = sstate.tmpcni[i];
+            }
+            
+            //
+            // Constrained Newton matrix: dense version.
+            //
+            if( sstate.akind==0 )
+            {
+                
+                //
+                // Update Cholesky matrix with SPDMatrixCholeskyUpdateFixBuf()
+                //
+                apserv.bvectorsetlengthatleast(ref sstate.tmpcnb, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    sstate.tmpcnb[i] = false;
+                }
+                for(i=nfree-ntofix; i<=nfree-1; i++)
+                {
+                    sstate.tmpcnb[sstate.yidx[i]] = true;
+                }
+                trfac.spdmatrixcholeskyupdatefixbuf(sstate.densez, n, true, sstate.tmpcnb, ref sstate.tmpcn, _params);
+                
+                //
+                // Update information stored in State and exit
+                //
+                sstate.nfree = nfree-ntofix;
+                sstate.cnmodelage = sstate.cnmodelage+ntofix;
+                ncupdates = ncupdates+ntofix;
+                result = true;
+                return result;
+            }
+            
+            //
+            // Unexpected :)
+            //
+            alglib.ap.assert(false, "CNewtonUpdate: internal error");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This   function prepares equality-constrained Newton step using previously
+        calculated constrained Cholesky matrix of the problem
+
+            f(x) = 0.5*x'*A*x + b'*x + penaltyfactor*0.5*(C*x-b)'*(C*x-b)
+            
+        where A can be dense or sparse.
+
+        As  input,  this  function  accepts  gradient  at the current location. As
+        output, it returns step vector (replaces gradient).
+
+        This function works as black box. It uses fields of SState which are marked
+        as "Variables for constrained Newton phase", and only  this  function  and
+        its friends know about these variables. Everyone else should use:
+        * CNewtonBuild() to prepare initial Cholesky decomposition for step
+        * CNewtonStep() to perform constrained Newton step
+        * CNewtonUpdate() to update Cholesky matrix  after  point  was  moved  and
+          constraints were updated. In some cases it  is  possible to  efficiently
+          re-calculate Cholesky decomposition if you know which  constraints  were
+          activated. If efficient  re-calculation  is  impossible,  this  function
+          returns False.
+
+        INPUT PARAMETERS:
+            SState  -   structure which stores model and temporaries for CN phase;
+                        in particular, SAS.XC stores current point.
+            Settings -  QQPSettings object which was  initialized  by  appropriate
+                        construction function.
+            GC       -  array[N], gradient of the target function
+            
+        OUTPUT PARAMETERS:
+            GC       -  array[N], step vector (on success)
+            
+        RESULT:
+            True, if step was successfully calculated.
+            False, if step calculation failed:
+            a) gradient was exactly zero,
+            b) gradient norm was smaller than EpsG (stopping condition)
+            c) all variables were equality-constrained
+            
+        NOTE: this function may routinely return False.
+              You should be able to handle such situations.
+                    
+          -- ALGLIB --
+             Copyright 14.05.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool cnewtonstep(qqpbuffers sstate,
+            qqpsettings settings,
+            double[] gc,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+            int n = 0;
+            int nfree = 0;
+            double v = 0;
+            int i_ = 0;
+
+            result = false;
+            n = sstate.n;
+            nfree = sstate.nfree;
+            for(i=nfree; i<=n-1; i++)
+            {
+                gc[sstate.yidx[i]] = 0.0;
+            }
+            v = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                v += gc[i_]*gc[i_];
+            }
+            if( (double)(Math.Sqrt(v))<=(double)(settings.epsg) )
+            {
+                return result;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                gc[i] = -gc[i];
+            }
+            if( sstate.akind==0 )
+            {
+                
+                //
+                // Dense Newton step.
+                // Use straightforward Cholesky solver.
+                //
+                fbls.fblscholeskysolve(sstate.densez, 1.0, n, true, gc, ref sstate.tmpcn, _params);
+                result = true;
+                return result;
+            }
+            if( sstate.akind==1 )
+            {
+                
+                //
+                // Sparse Newton step.
+                //
+                // We have T*T' = L*L' = U'*U (depending on specific triangle stored in SparseCCA).
+                //
+                if( sstate.sparseupper )
+                {
+                    sparse.sparsetrsv(sstate.sparsecca, sstate.sparseupper, false, 1, gc, _params);
+                    sparse.sparsetrsv(sstate.sparsecca, sstate.sparseupper, false, 0, gc, _params);
+                }
+                else
+                {
+                    sparse.sparsetrsv(sstate.sparsecca, sstate.sparseupper, false, 0, gc, _params);
+                    sparse.sparsetrsv(sstate.sparsecca, sstate.sparseupper, false, 1, gc, _params);
+                }
+                result = true;
+                return result;
+            }
+            alglib.ap.assert(false, "CNewtonStep: internal error");
+            return result;
+        }
+
+
+    }
+    public class minlbfgs
+    {
+        public class minlbfgsstate : apobject
+        {
+            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[] xp;
+            public double[] theta;
+            public double[] d;
+            public double stp;
+            public double[] work;
+            public double fold;
+            public double trimthreshold;
+            public double[] xbase;
+            public int prectype;
+            public double gammak;
+            public double[,] denseh;
+            public double[] diagh;
+            public double[] precc;
+            public double[] precd;
+            public double[,] precw;
+            public int preck;
+            public optserv.precbuflbfgs precbuf;
+            public optserv.precbuflowrank lowrankbuf;
+            public double fbase;
+            public double fm2;
+            public double fm1;
+            public double fp1;
+            public double fp2;
+            public double[] autobuf;
+            public double[] invs;
+            public double[] x;
+            public double f;
+            public double[] g;
+            public bool needf;
+            public bool needfg;
+            public bool xupdated;
+            public bool userterminationneeded;
+            public double teststep;
+            public rcommstate rstate;
+            public int repiterationscount;
+            public int repnfev;
+            public int repterminationtype;
+            public linmin.linminstate lstate;
+            public int smoothnessguardlevel;
+            public optserv.smoothnessmonitor smonitor;
+            public double[] lastscaleused;
+            public minlbfgsstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                s = new double[0];
+                rho = new double[0];
+                yk = new double[0,0];
+                sk = new double[0,0];
+                xp = new double[0];
+                theta = new double[0];
+                d = new double[0];
+                work = new double[0];
+                xbase = new double[0];
+                denseh = new double[0,0];
+                diagh = new double[0];
+                precc = new double[0];
+                precd = new double[0];
+                precw = new double[0,0];
+                precbuf = new optserv.precbuflbfgs();
+                lowrankbuf = new optserv.precbuflowrank();
+                autobuf = new double[0];
+                invs = new double[0];
+                x = new double[0];
+                g = new double[0];
+                rstate = new rcommstate();
+                lstate = new linmin.linminstate();
+                smonitor = new optserv.smoothnessmonitor();
+                lastscaleused = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                minlbfgsstate _result = new minlbfgsstate();
+                _result.n = n;
+                _result.m = m;
+                _result.epsg = epsg;
+                _result.epsf = epsf;
+                _result.epsx = epsx;
+                _result.maxits = maxits;
+                _result.xrep = xrep;
+                _result.stpmax = stpmax;
+                _result.s = (double[])s.Clone();
+                _result.diffstep = diffstep;
+                _result.nfev = nfev;
+                _result.mcstage = mcstage;
+                _result.k = k;
+                _result.q = q;
+                _result.p = p;
+                _result.rho = (double[])rho.Clone();
+                _result.yk = (double[,])yk.Clone();
+                _result.sk = (double[,])sk.Clone();
+                _result.xp = (double[])xp.Clone();
+                _result.theta = (double[])theta.Clone();
+                _result.d = (double[])d.Clone();
+                _result.stp = stp;
+                _result.work = (double[])work.Clone();
+                _result.fold = fold;
+                _result.trimthreshold = trimthreshold;
+                _result.xbase = (double[])xbase.Clone();
+                _result.prectype = prectype;
+                _result.gammak = gammak;
+                _result.denseh = (double[,])denseh.Clone();
+                _result.diagh = (double[])diagh.Clone();
+                _result.precc = (double[])precc.Clone();
+                _result.precd = (double[])precd.Clone();
+                _result.precw = (double[,])precw.Clone();
+                _result.preck = preck;
+                _result.precbuf = (optserv.precbuflbfgs)precbuf.make_copy();
+                _result.lowrankbuf = (optserv.precbuflowrank)lowrankbuf.make_copy();
+                _result.fbase = fbase;
+                _result.fm2 = fm2;
+                _result.fm1 = fm1;
+                _result.fp1 = fp1;
+                _result.fp2 = fp2;
+                _result.autobuf = (double[])autobuf.Clone();
+                _result.invs = (double[])invs.Clone();
+                _result.x = (double[])x.Clone();
+                _result.f = f;
+                _result.g = (double[])g.Clone();
+                _result.needf = needf;
+                _result.needfg = needfg;
+                _result.xupdated = xupdated;
+                _result.userterminationneeded = userterminationneeded;
+                _result.teststep = teststep;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.repiterationscount = repiterationscount;
+                _result.repnfev = repnfev;
+                _result.repterminationtype = repterminationtype;
+                _result.lstate = (linmin.linminstate)lstate.make_copy();
+                _result.smoothnessguardlevel = smoothnessguardlevel;
+                _result.smonitor = (optserv.smoothnessmonitor)smonitor.make_copy();
+                _result.lastscaleused = (double[])lastscaleused.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure stores optimization report:
+        * IterationsCount           total number of inner iterations
+        * NFEV                      number of gradient evaluations
+        * TerminationType           termination type (see below)
+
+        TERMINATION CODES
+
+        TerminationType field contains completion code, which can be:
+          -8    internal integrity control detected  infinite  or  NAN  values  in
+                function/gradient. Abnormal termination signalled.
+           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,
+                X contains best point found so far.
+           8    terminated    by  user  who  called  minlbfgsrequesttermination().
+                X contains point which was   "current accepted"  when  termination
+                request was submitted.
+                
+        Other fields of this structure are not documented and should not be used!
+        *************************************************************************/
+        public class minlbfgsreport : apobject
+        {
+            public int iterationscount;
+            public int nfev;
+            public int terminationtype;
+            public minlbfgsreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                minlbfgsreport _result = new minlbfgsreport();
+                _result.iterationscount = iterationscount;
+                _result.nfev = nfev;
+                _result.terminationtype = terminationtype;
+                return _result;
+            }
+        };
+
+
+
+
+        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.xparams _params)
+        {
+            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, _params), "MinLBFGSCreate: X contains infinite or NaN values!");
+            minlbfgscreatex(n, m, x, 0, 0.0, state, _params);
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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.teststep = 0;
+            state.smoothnessguardlevel = 0;
+            optserv.smoothnessmonitorinit(state.smonitor, 0, 0, false, _params);
+            state.diffstep = diffstep;
+            state.n = n;
+            state.m = m;
+            allocatemem = flags%2==0;
+            flags = flags/2;
+            if( allocatemem )
+            {
+                apserv.rvectorsetlengthatleast(ref state.rho, m, _params);
+                apserv.rvectorsetlengthatleast(ref state.theta, m, _params);
+                apserv.rmatrixsetlengthatleast(ref state.yk, m, n, _params);
+                apserv.rmatrixsetlengthatleast(ref state.sk, m, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.d, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.xp, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.x, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.xbase, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.s, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.invs, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.lastscaleused, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.g, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.work, n, _params);
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.s[i] = 1.0;
+                state.invs[i] = 1.0;
+                state.lastscaleused[i] = 1.0;
+            }
+            state.prectype = 0;
+            minlbfgssetcond(state, 0, 0, 0, 0, _params);
+            minlbfgssetxrep(state, false, _params);
+            minlbfgssetstpmax(state, 0, _params);
+            minlbfgsrestartfrom(state, x, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double mx = 0;
+
+            alglib.ap.assert(apserv.isfinitertrmatrix(p, state.n, isupper, _params), "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, state.denseh, 0, 0, _params);
+            }
+            else
+            {
+                ablas.rmatrixtranspose(state.n, state.n, p, 0, 0, state.denseh, 0, 0, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            state.prectype = 3;
+        }
+
+
+        /*************************************************************************
+        This function sets low-rank preconditioner for Hessian matrix  H=D+W'*C*W,
+        where:
+        * H is a Hessian matrix, which is approximated by D/W/C
+        * D is a NxN diagonal positive definite matrix
+        * W is a KxN low-rank correction
+        * C is a KxK positive definite diagonal factor of low-rank correction
+
+        This preconditioner is inexact but fast - it requires O(N*K)  time  to  be
+        applied. Preconditioner P is calculated by artificially constructing a set
+        of BFGS updates which tries to reproduce behavior of H:
+        * Sk = Wk (k-th row of W)
+        * Yk = (D+Wk'*Ck*Wk)*Sk
+        * Yk/Sk are reordered by ascending of C[k]*norm(Wk)^2
+
+        Here we assume that rows of Wk are orthogonal or nearly orthogonal,  which
+        allows us to have O(N*K+K^2) update instead of O(N*K^2) one. Reordering of
+        updates is essential for having good performance on non-orthogonal problems
+        (updates which do not add much of curvature are added first,  and  updates
+        which add very large eigenvalues are added last and override effect of the
+        first updates).
+
+        In practice, this preconditioner is perfect when ortogonal  correction  is
+        applied; on non-orthogonal problems sometimes  it  allows  to  achieve  5x
+        speedup (when compared to non-preconditioned solver).
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetprecrankklbfgsfast(minlbfgsstate state,
+            double[] d,
+            double[] c,
+            double[,] w,
+            int cnt,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+
+            n = state.n;
+            state.prectype = 4;
+            state.preck = cnt;
+            apserv.rvectorsetlengthatleast(ref state.precc, cnt, _params);
+            apserv.rvectorsetlengthatleast(ref state.precd, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.precw, cnt, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.precd[i] = d[i];
+            }
+            for(i=0; i<=cnt-1; i++)
+            {
+                state.precc[i] = c[i];
+                for(j=0; j<=n-1; j++)
+                {
+                    state.precw[i,j] = w[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function  sets  exact  low-rank  preconditioner  for  Hessian  matrix
+        H=D+W'*C*W, where:
+        * H is a Hessian matrix, which is approximated by D/W/C
+        * D is a NxN diagonal positive definite matrix
+        * W is a KxN low-rank correction
+        * C is a KxK semidefinite diagonal factor of low-rank correction
+
+        This preconditioner is exact but slow - it requires O(N*K^2)  time  to  be
+        built and O(N*K) time to be applied. Woodbury matrix identity is  used  to
+        build inverse matrix.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetpreclowrankexact(minlbfgsstate state,
+            double[] d,
+            double[] c,
+            double[,] w,
+            int cnt,
+            alglib.xparams _params)
+        {
+            state.prectype = 5;
+            optserv.preparelowrankpreconditioner(d, c, w, state.n, cnt, state.lowrankbuf, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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 = 359;
+                m = -58;
+                i = -919;
+                j = -909;
+                ic = 81;
+                mcinfo = 255;
+                v = 74;
+                vv = -788;
+            }
+            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
+            //
+            
+            //
+            // Unload frequently used variables from State structure
+            // (just for typing convinience)
+            //
+            n = state.n;
+            m = state.m;
+            
+            //
+            // Init
+            //
+            state.userterminationneeded = false;
+            state.repterminationtype = 0;
+            state.repiterationscount = 0;
+            state.repnfev = 0;
+            optserv.smoothnessmonitorinit(state.smonitor, n, 1, state.smoothnessguardlevel>0, _params);
+            apserv.rvectorsetlengthatleast(ref state.invs, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.lastscaleused[i] = state.s[i];
+                state.invs[i] = 1/state.s[i];
+            }
+            
+            //
+            //  Check, that transferred derivative value is right
+            //
+            state.stp = 0;
+            clearrequestfields(state, _params);
+            if( !((double)(state.diffstep)==(double)(0) && (double)(state.teststep)>(double)(0)) )
+            {
+                goto lbl_15;
+            }
+        lbl_17:
+            if( !optserv.smoothnessmonitorcheckgradientatx0(state.smonitor, state.xbase, state.s, state.s, state.s, false, state.teststep, _params) )
+            {
+                goto lbl_18;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.smonitor.x[i];
+            }
+            state.needfg = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfg = false;
+            state.smonitor.fi[0] = state.f;
+            for(i=0; i<=n-1; i++)
+            {
+                state.smonitor.j[0,i] = state.g[i];
+            }
+            goto lbl_17;
+        lbl_18:
+        lbl_15:
+            
+            //
+            // Calculate F/G at the initial point
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.xbase[i];
+            }
+            state.stp = 0;
+            clearrequestfields(state, _params);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_19;
+            }
+            state.needfg = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.needfg = false;
+            goto lbl_20;
+        lbl_19:
+            state.needf = true;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.fbase = state.f;
+            i = 0;
+        lbl_21:
+            if( i>n-1 )
+            {
+                goto lbl_23;
+            }
+            v = state.x[i];
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            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_21;
+        lbl_23:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_20:
+            optserv.trimprepare(state.f, ref state.trimthreshold, _params);
+            if( !state.xrep )
+            {
+                goto lbl_24;
+            }
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.xupdated = false;
+        lbl_24:
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            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, state.d, ref state.autobuf, _params);
+                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;
+            }
+            if( state.prectype==4 )
+            {
+                
+                //
+                // rank-k BFGS-based preconditioner is used
+                //
+                optserv.inexactlbfgspreconditioner(state.d, n, state.precd, state.precc, state.precw, state.preck, state.precbuf, _params);
+                state.stp = 1;
+            }
+            if( state.prectype==5 )
+            {
+                
+                //
+                // exact low-rank preconditioner is used
+                //
+                optserv.applylowrankpreconditioner(state.d, state.lowrankbuf, _params);
+                state.stp = 1;
+            }
+            
+            //
+            // Main cycle
+            //
+            state.k = 0;
+        lbl_26:
+            if( false )
+            {
+                goto lbl_27;
+            }
+            
+            //
+            // 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.xp[i_] = state.x[i_];
+            }
+            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, _params);
+            optserv.smoothnessmonitorstartlinesearch1u(state.smonitor, state.s, state.invs, state.x, state.f, state.g, _params);
+            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, _params);
+        lbl_28:
+            if( state.mcstage==0 )
+            {
+                goto lbl_29;
+            }
+            clearrequestfields(state, _params);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_30;
+            }
+            state.needfg = true;
+            state.rstate.stage = 8;
+            goto lbl_rcomm;
+        lbl_8:
+            state.needfg = false;
+            goto lbl_31;
+        lbl_30:
+            state.needf = true;
+            state.rstate.stage = 9;
+            goto lbl_rcomm;
+        lbl_9:
+            state.fbase = state.f;
+            i = 0;
+        lbl_32:
+            if( i>n-1 )
+            {
+                goto lbl_34;
+            }
+            v = state.x[i];
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 10;
+            goto lbl_rcomm;
+        lbl_10:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 11;
+            goto lbl_rcomm;
+        lbl_11:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 12;
+            goto lbl_rcomm;
+        lbl_12:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 13;
+            goto lbl_rcomm;
+        lbl_13:
+            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_32;
+        lbl_34:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_31:
+            optserv.smoothnessmonitorenqueuepoint1u(state.smonitor, state.s, state.invs, state.d, state.stp, state.x, state.f, state.g, _params);
+            optserv.trimfunction(ref state.f, ref state.g, n, state.trimthreshold, _params);
+            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, _params);
+            goto lbl_28;
+        lbl_29:
+            optserv.smoothnessmonitorfinalizelinesearch(state.smonitor, _params);
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination.
+                // Restore previous point and return.
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.x[i_] = state.xp[i_];
+                }
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            if( !state.xrep )
+            {
+                goto lbl_35;
+            }
+            
+            //
+            // report
+            //
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 14;
+            goto lbl_rcomm;
+        lbl_14:
+            state.xupdated = false;
+        lbl_35:
+            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
+            //
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.g[i]*state.s[i]);
+            }
+            if( !math.isfinite(v) || !math.isfinite(state.f) )
+            {
+                
+                //
+                // Abnormal termination - infinities in function/gradient
+                //
+                state.repterminationtype = -8;
+                result = false;
+                return result;
+            }
+            if( state.repiterationscount>=state.maxits && state.maxits>0 )
+            {
+                
+                //
+                // Too many iterations
+                //
+                state.repterminationtype = 5;
+                result = false;
+                return result;
+            }
+            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, state.work, ref state.autobuf, _params);
+                }
+                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];
+                    }
+                }
+                if( state.prectype==4 )
+                {
+                    
+                    //
+                    // Rank-K BFGS-based preconditioner is used
+                    //
+                    optserv.inexactlbfgspreconditioner(state.work, n, state.precd, state.precc, state.precw, state.preck, state.precbuf, _params);
+                }
+                if( state.prectype==5 )
+                {
+                    
+                    //
+                    // Exact low-rank preconditioner is used
+                    //
+                    optserv.applylowrankpreconditioner(state.work, state.lowrankbuf, _params);
+                }
+                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_26;
+        lbl_27:
+            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;
+        }
+
+
+        /*************************************************************************
+        This  function  activates/deactivates verification  of  the  user-supplied
+        analytic gradient.
+
+        Upon  activation  of  this  option  OptGuard  integrity  checker  performs
+        numerical differentiation of your target function  at  the  initial  point
+        (note: future versions may also perform check  at  the  final  point)  and
+        compares numerical gradient with analytic one provided by you.
+
+        If difference is too large, an error flag is set and optimization  session
+        continues. After optimization session is over, you can retrieve the report
+        which  stores  both  gradients  and  specific  components  highlighted  as
+        suspicious by the OptGuard.
+
+        The primary OptGuard report can be retrieved with minlbfgsoptguardresults().
+
+        IMPORTANT: gradient check is a high-overhead option which  will  cost  you
+                   about 3*N additional function evaluations. In many cases it may
+                   cost as much as the rest of the optimization session.
+                   
+                   YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO
+                   CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY.
+
+        NOTE: unlike previous incarnation of the gradient checking code,  OptGuard
+              does NOT interrupt optimization even if it discovers bad gradient.
+              
+        INPUT PARAMETERS:
+            State       -   structure used to store algorithm state
+            TestStep    -   verification step used for numerical differentiation:
+                            * TestStep=0 turns verification off
+                            * TestStep>0 activates verification
+                            You should carefully choose TestStep. Value  which  is
+                            too large (so large that  function  behavior  is  non-
+                            cubic at this scale) will lead  to  false  alarms. Too
+                            short step will result in rounding  errors  dominating
+                            numerical derivative.
+                            
+                            You may use different step for different parameters by
+                            means of setting scale with minlbfgssetscale().
+
+        === EXPLANATION ==========================================================                    
+
+        In order to verify gradient algorithm performs following steps:
+          * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i],
+            where X[i] is i-th component of the initial point and S[i] is a  scale
+            of i-th parameter
+          * F(X) is evaluated at these trial points
+          * we perform one more evaluation in the middle point of the interval
+          * we  build  cubic  model using function values and derivatives at trial
+            points and we compare its prediction with actual value in  the  middle
+            point
+
+          -- ALGLIB --
+             Copyright 15.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgsoptguardgradient(minlbfgsstate state,
+            double teststep,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(teststep), "MinLBFGSOptGuardGradient: TestStep contains NaN or INF");
+            alglib.ap.assert((double)(teststep)>=(double)(0), "MinLBFGSOptGuardGradient: invalid argument TestStep(TestStep<0)");
+            state.teststep = teststep;
+        }
+
+
+        /*************************************************************************
+        This  function  activates/deactivates nonsmoothness monitoring  option  of
+        the  OptGuard  integrity  checker. Smoothness  monitor  silently  observes
+        solution process and tries to detect ill-posed problems, i.e. ones with:
+        a) discontinuous target function (non-C0)
+        b) nonsmooth     target function (non-C1)
+
+        Smoothness monitoring does NOT interrupt optimization  even if it suspects
+        that your problem is nonsmooth. It just sets corresponding  flags  in  the
+        OptGuard report which can be retrieved after optimization is over.
+
+        Smoothness monitoring is a moderate overhead option which often adds  less
+        than 1% to the optimizer running time. Thus, you can use it even for large
+        scale problems.
+
+        NOTE: OptGuard does  NOT  guarantee  that  it  will  always  detect  C0/C1
+              continuity violations.
+              
+              First, minor errors are hard to  catch - say, a 0.0001 difference in
+              the model values at two sides of the gap may be due to discontinuity
+              of the model - or simply because the model has changed.
+              
+              Second, C1-violations  are  especially  difficult  to  detect  in  a
+              noninvasive way. The optimizer usually  performs  very  short  steps
+              near the nonsmoothness, and differentiation  usually   introduces  a
+              lot of numerical noise.  It  is  hard  to  tell  whether  some  tiny
+              discontinuity in the slope is due to real nonsmoothness or just  due
+              to numerical noise alone.
+              
+              Our top priority was to avoid false positives, so in some rare cases
+              minor errors may went unnoticed (however, in most cases they can  be
+              spotted with restart from different initial point).
+
+        INPUT PARAMETERS:
+            state   -   algorithm state
+            level   -   monitoring level:
+                        * 0 - monitoring is disabled
+                        * 1 - noninvasive low-overhead monitoring; function values
+                              and/or gradients are recorded, but OptGuard does not
+                              try to perform additional evaluations  in  order  to
+                              get more information about suspicious locations.
+          
+        === EXPLANATION ==========================================================
+
+        One major source of headache during optimization  is  the  possibility  of
+        the coding errors in the target function/constraints (or their gradients).
+        Such  errors   most   often   manifest   themselves  as  discontinuity  or
+        nonsmoothness of the target/constraints.
+
+        Another frequent situation is when you try to optimize something involving
+        lots of min() and max() operations, i.e. nonsmooth target. Although not  a
+        coding error, it is nonsmoothness anyway - and smooth  optimizers  usually
+        stop right after encountering nonsmoothness, well before reaching solution.
+
+        OptGuard integrity checker helps you to catch such situations: it monitors
+        function values/gradients being passed  to  the  optimizer  and  tries  to
+        errors. Upon discovering suspicious pair of points it  raises  appropriate
+        flag (and allows you to continue optimization). When optimization is done,
+        you can study OptGuard result.
+
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgsoptguardsmoothness(minlbfgsstate state,
+            int level,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(level==0 || level==1, "MinLBFGSOptGuardSmoothness: unexpected value of level parameter");
+            state.smoothnessguardlevel = level;
+        }
+
+
+        /*************************************************************************
+        Results of OptGuard integrity check, should be called  after  optimization
+        session is over.
+
+        === PRIMARY REPORT =======================================================
+
+        OptGuard performs several checks which are intended to catch common errors
+        in the implementation of nonlinear function/gradient:
+        * incorrect analytic gradient
+        * discontinuous (non-C0) target functions (constraints)
+        * nonsmooth     (non-C1) target functions (constraints)
+
+        Each of these checks is activated with appropriate function:
+        * minlbfgsoptguardgradient() for gradient verification
+        * minlbfgsoptguardsmoothness() for C0/C1 checks
+
+        Following flags are set when these errors are suspected:
+        * rep.badgradsuspected, and additionally:
+          * rep.badgradvidx for specific variable (gradient element) suspected
+          * rep.badgradxbase, a point where gradient is tested
+          * rep.badgraduser, user-provided gradient  (stored  as  2D  matrix  with
+            single row in order to make  report  structure  compatible  with  more
+            complex optimizers like MinNLC or MinLM)
+          * rep.badgradnum,   reference    gradient    obtained    via   numerical
+            differentiation (stored as  2D matrix with single row in order to make
+            report structure compatible with more complex optimizers  like  MinNLC
+            or MinLM)
+        * rep.nonc0suspected
+        * rep.nonc1suspected
+
+        === ADDITIONAL REPORTS/LOGS ==============================================
+            
+        Several different tests are performed to catch C0/C1 errors, you can  find
+        out specific test signaled error by looking to:
+        * rep.nonc0test0positive, for non-C0 test #0
+        * rep.nonc1test0positive, for non-C1 test #0
+        * rep.nonc1test1positive, for non-C1 test #1
+
+        Additional information (including line search logs)  can  be  obtained  by
+        means of:
+        * minlbfgsoptguardnonc1test0results()
+        * minlbfgsoptguardnonc1test1results()
+        which return detailed error reports, specific points where discontinuities
+        were found, and so on.
+
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            rep     -   generic OptGuard report;  more  detailed  reports  can  be
+                        retrieved with other functions.
+
+        NOTE: false negatives (nonsmooth problems are not identified as  nonsmooth
+              ones) are possible although unlikely.
+              
+              The reason  is  that  you  need  to  make several evaluations around
+              nonsmoothness  in  order  to  accumulate  enough  information  about
+              function curvature. Say, if you start right from the nonsmooth point,
+              optimizer simply won't get enough data to understand what  is  going
+              wrong before it terminates due to abrupt changes in the  derivative.
+              It is also  possible  that  "unlucky"  step  will  move  us  to  the
+              termination too quickly.
+              
+              Our current approach is to have less than 0.1%  false  negatives  in
+              our test examples  (measured  with  multiple  restarts  from  random
+              points), and to have exactly 0% false positives.
+           
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgsoptguardresults(minlbfgsstate state,
+            optguardapi.optguardreport rep,
+            alglib.xparams _params)
+        {
+            optserv.smoothnessmonitorexportreport(state.smonitor, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Detailed results of the OptGuard integrity check for nonsmoothness test #0
+
+        Nonsmoothness (non-C1) test #0 studies  function  values  (not  gradient!)
+        obtained during line searches and monitors  behavior  of  the  directional
+        derivative estimate.
+
+        This test is less powerful than test #1, but it does  not  depend  on  the
+        gradient values and thus it is more robust against artifacts introduced by
+        numerical differentiation.
+
+        Two reports are returned:
+        * a "strongest" one, corresponding  to  line   search  which  had  highest
+          value of the nonsmoothness indicator
+        * a "longest" one, corresponding to line search which  had  more  function
+          evaluations, and thus is more detailed
+
+        In both cases following fields are returned:
+
+        * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+          did not notice anything (in the latter cases fields below are empty).
+        * x0[], d[] - arrays of length N which store initial point  and  direction
+          for line search (d[] can be normalized, but does not have to)
+        * stp[], f[] - arrays of length CNT which store step lengths and  function
+          values at these points; f[i] is evaluated in x0+stp[i]*d.
+        * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+          between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+          with  most  likely  position  of  the  violation  between  stpidxa+1 and
+          stpidxa+2.
+          
+        ==========================================================================
+        = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+        =                   see where C1 continuity is violated.
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            strrep  -   C1 test #0 "strong" report
+            lngrep  -   C1 test #0 "long" report
+            
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgsoptguardnonc1test0results(minlbfgsstate state,
+            optguardapi.optguardnonc1test0report strrep,
+            optguardapi.optguardnonc1test0report lngrep,
+            alglib.xparams _params)
+        {
+            optguardapi.smoothnessmonitorexportc1test0report(state.smonitor.nonc1test0strrep, state.lastscaleused, strrep, _params);
+            optguardapi.smoothnessmonitorexportc1test0report(state.smonitor.nonc1test0lngrep, state.lastscaleused, lngrep, _params);
+        }
+
+
+        /*************************************************************************
+        Detailed results of the OptGuard integrity check for nonsmoothness test #1
+
+        Nonsmoothness (non-C1)  test  #1  studies  individual  components  of  the
+        gradient computed during line search.
+
+        When precise analytic gradient is provided this test is more powerful than
+        test #0  which  works  with  function  values  and  ignores  user-provided
+        gradient.  However,  test  #0  becomes  more   powerful   when   numerical
+        differentiation is employed (in such cases test #1 detects  higher  levels
+        of numerical noise and becomes too conservative).
+
+        This test also tells specific components of the gradient which violate  C1
+        continuity, which makes it more informative than #0, which just tells that
+        continuity is violated.
+
+        Two reports are returned:
+        * a "strongest" one, corresponding  to  line   search  which  had  highest
+          value of the nonsmoothness indicator
+        * a "longest" one, corresponding to line search which  had  more  function
+          evaluations, and thus is more detailed
+
+        In both cases following fields are returned:
+
+        * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+          did not notice anything (in the latter cases fields below are empty).
+        * vidx - is an index of the variable in [0,N) with nonsmooth derivative
+        * x0[], d[] - arrays of length N which store initial point  and  direction
+          for line search (d[] can be normalized, but does not have to)
+        * stp[], g[] - arrays of length CNT which store step lengths and  gradient
+          values at these points; g[i] is evaluated in  x0+stp[i]*d  and  contains
+          vidx-th component of the gradient.
+        * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+          between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+          with  most  likely  position  of  the  violation  between  stpidxa+1 and
+          stpidxa+2.
+          
+        ==========================================================================
+        = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+        =                   see where C1 continuity is violated.
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            strrep  -   C1 test #1 "strong" report
+            lngrep  -   C1 test #1 "long" report
+            
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgsoptguardnonc1test1results(minlbfgsstate state,
+            optguardapi.optguardnonc1test1report strrep,
+            optguardapi.optguardnonc1test1report lngrep,
+            alglib.xparams _params)
+        {
+            optguardapi.smoothnessmonitorexportc1test1report(state.smonitor.nonc1test1strrep, state.lastscaleused, strrep, _params);
+            optguardapi.smoothnessmonitorexportc1test1report(state.smonitor.nonc1test1lngrep, state.lastscaleused, lngrep, _params);
+        }
+
+
+        /*************************************************************************
+        L-BFGS algorithm results
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[0..N-1], solution
+            Rep     -   optimization report:
+                        * Rep.TerminationType completetion code:
+                            * -8    internal integrity control  detected  infinite
+                                    or NAN values in  function/gradient.  Abnormal
+                                    termination signalled.
+                            * -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
+                            *  8    terminated by user who called minlbfgsrequesttermination().
+                                    X contains point which was "current accepted" when
+                                    termination request was submitted.
+                        * 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,
+            alglib.xparams _params)
+        {
+            x = new double[0];
+
+            minlbfgsresultsbuf(state, ref x, rep, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=state.n, "MinLBFGSRestartFrom: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, state.n, _params), "MinLBFGSRestartFrom: X contains infinite or NaN values!");
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.xbase[i_] = x[i_];
+            }
+            state.rstate.ia = new int[5+1];
+            state.rstate.ra = new double[1+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine submits request for termination of running  optimizer.  It
+        should be called from user-supplied callback when user decides that it  is
+        time to "smoothly" terminate optimization process.  As  result,  optimizer
+        stops at point which was "current accepted" when termination  request  was
+        submitted and returns error code 8 (successful termination).
+
+        INPUT PARAMETERS:
+            State   -   optimizer structure
+
+        NOTE: after  request  for  termination  optimizer  may   perform   several
+              additional calls to user-supplied callbacks. It does  NOT  guarantee
+              to stop immediately - it just guarantees that these additional calls
+              will be discarded later.
+
+        NOTE: calling this function on optimizer which is NOT running will have no
+              effect.
+              
+        NOTE: multiple calls to this function are possible. First call is counted,
+              subsequent calls are silently ignored.
+
+          -- ALGLIB --
+             Copyright 08.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgsrequesttermination(minlbfgsstate state,
+            alglib.xparams _params)
+        {
+            state.userterminationneeded = true;
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(minlbfgsstate state,
+            alglib.xparams _params)
+        {
+            state.needf = false;
+            state.needfg = false;
+            state.xupdated = false;
+        }
+
+
+    }
+    public class qpdenseaulsolver
+    {
+        /*************************************************************************
+        This object stores settings for DENSE-AUL solver.
+        It must be initialized with QPDENSEAULLoadDefaults().
+        After initialization you may change settings.
+        *************************************************************************/
+        public class qpdenseaulsettings : apobject
+        {
+            public double epsx;
+            public int outerits;
+            public double rho;
+            public qpdenseaulsettings()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                qpdenseaulsettings _result = new qpdenseaulsettings();
+                _result.epsx = epsx;
+                _result.outerits = outerits;
+                _result.rho = rho;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This object stores temporaries used by Dense-AUL solver.
+        *************************************************************************/
+        public class qpdenseaulbuffers : apobject
+        {
+            public double[] nulc;
+            public double[,] sclsfta;
+            public double[] sclsftb;
+            public bool[] sclsfthasbndl;
+            public bool[] sclsfthasbndu;
+            public double[] sclsftbndl;
+            public double[] sclsftbndu;
+            public double[] sclsftxc;
+            public double[,] sclsftcleic;
+            public double[,] exa;
+            public double[] exb;
+            public double[] exxc;
+            public double[] exbndl;
+            public double[] exbndu;
+            public double[] exscale;
+            public double[] exxorigin;
+            public qqpsolver.qqpsettings qqpsettingsuser;
+            public qqpsolver.qqpbuffers qqpbuf;
+            public double[] nulcest;
+            public double[] tmp0;
+            public double[,] tmp2;
+            public double[] modelg;
+            public double[] d;
+            public double[] deltax;
+            public cqmodels.convexquadraticmodel dummycqm;
+            public sparse.sparsematrix dummysparse;
+            public double[,] qrkkt;
+            public double[] qrrightpart;
+            public double[] qrtau;
+            public double[] qrsv0;
+            public double[] qrsvx1;
+            public double[] nicerr;
+            public int[] nicnact;
+            public int repinneriterationscount;
+            public int repouteriterationscount;
+            public int repncholesky;
+            public int repnwrkchanges;
+            public int repnwrk0;
+            public int repnwrk1;
+            public int repnwrkf;
+            public int repnmv;
+            public qpdenseaulbuffers()
+            {
+                init();
+            }
+            public override void init()
+            {
+                nulc = new double[0];
+                sclsfta = new double[0,0];
+                sclsftb = new double[0];
+                sclsfthasbndl = new bool[0];
+                sclsfthasbndu = new bool[0];
+                sclsftbndl = new double[0];
+                sclsftbndu = new double[0];
+                sclsftxc = new double[0];
+                sclsftcleic = new double[0,0];
+                exa = new double[0,0];
+                exb = new double[0];
+                exxc = new double[0];
+                exbndl = new double[0];
+                exbndu = new double[0];
+                exscale = new double[0];
+                exxorigin = new double[0];
+                qqpsettingsuser = new qqpsolver.qqpsettings();
+                qqpbuf = new qqpsolver.qqpbuffers();
+                nulcest = new double[0];
+                tmp0 = new double[0];
+                tmp2 = new double[0,0];
+                modelg = new double[0];
+                d = new double[0];
+                deltax = new double[0];
+                dummycqm = new cqmodels.convexquadraticmodel();
+                dummysparse = new sparse.sparsematrix();
+                qrkkt = new double[0,0];
+                qrrightpart = new double[0];
+                qrtau = new double[0];
+                qrsv0 = new double[0];
+                qrsvx1 = new double[0];
+                nicerr = new double[0];
+                nicnact = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                qpdenseaulbuffers _result = new qpdenseaulbuffers();
+                _result.nulc = (double[])nulc.Clone();
+                _result.sclsfta = (double[,])sclsfta.Clone();
+                _result.sclsftb = (double[])sclsftb.Clone();
+                _result.sclsfthasbndl = (bool[])sclsfthasbndl.Clone();
+                _result.sclsfthasbndu = (bool[])sclsfthasbndu.Clone();
+                _result.sclsftbndl = (double[])sclsftbndl.Clone();
+                _result.sclsftbndu = (double[])sclsftbndu.Clone();
+                _result.sclsftxc = (double[])sclsftxc.Clone();
+                _result.sclsftcleic = (double[,])sclsftcleic.Clone();
+                _result.exa = (double[,])exa.Clone();
+                _result.exb = (double[])exb.Clone();
+                _result.exxc = (double[])exxc.Clone();
+                _result.exbndl = (double[])exbndl.Clone();
+                _result.exbndu = (double[])exbndu.Clone();
+                _result.exscale = (double[])exscale.Clone();
+                _result.exxorigin = (double[])exxorigin.Clone();
+                _result.qqpsettingsuser = (qqpsolver.qqpsettings)qqpsettingsuser.make_copy();
+                _result.qqpbuf = (qqpsolver.qqpbuffers)qqpbuf.make_copy();
+                _result.nulcest = (double[])nulcest.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmp2 = (double[,])tmp2.Clone();
+                _result.modelg = (double[])modelg.Clone();
+                _result.d = (double[])d.Clone();
+                _result.deltax = (double[])deltax.Clone();
+                _result.dummycqm = (cqmodels.convexquadraticmodel)dummycqm.make_copy();
+                _result.dummysparse = (sparse.sparsematrix)dummysparse.make_copy();
+                _result.qrkkt = (double[,])qrkkt.Clone();
+                _result.qrrightpart = (double[])qrrightpart.Clone();
+                _result.qrtau = (double[])qrtau.Clone();
+                _result.qrsv0 = (double[])qrsv0.Clone();
+                _result.qrsvx1 = (double[])qrsvx1.Clone();
+                _result.nicerr = (double[])nicerr.Clone();
+                _result.nicnact = (int[])nicnact.Clone();
+                _result.repinneriterationscount = repinneriterationscount;
+                _result.repouteriterationscount = repouteriterationscount;
+                _result.repncholesky = repncholesky;
+                _result.repnwrkchanges = repnwrkchanges;
+                _result.repnwrk0 = repnwrk0;
+                _result.repnwrk1 = repnwrk1;
+                _result.repnwrkf = repnwrkf;
+                _result.repnmv = repnmv;
+                return _result;
+            }
+        };
+
+
+
+
+        public const double evictionlevel = -0.01;
+        public const double expansionratio = 0.20;
+
+
+        /*************************************************************************
+        This function initializes QPDENSEAULSettings structure with default settings.
+
+        Newly created structure MUST be initialized by default settings  -  or  by
+        copy of the already initialized structure.
+
+          -- ALGLIB --
+             Copyright 14.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void qpdenseaulloaddefaults(int nmain,
+            qpdenseaulsettings s,
+            alglib.xparams _params)
+        {
+            s.epsx = 1.0E-6;
+            s.outerits = 5;
+            s.rho = 100.0;
+        }
+
+
+        /*************************************************************************
+        This function runs Dense-AUL solver; it returns after optimization process
+        was completed. Following QP problem is solved:
+
+            min(0.5*(x-x_origin)'*A*(x-x_origin)+b'*(x-x_origin))
+            
+        subject to combination of box and general linear dense/sparse constraints.
+
+        INPUT PARAMETERS:
+            DenseA      -   for dense problems (AKind=0), A-term of CQM object
+                            contains system matrix. Other terms are unspecified
+                            and should not be referenced.
+            SparseA     -   for sparse problems (AKind=1), CRS format
+            AKind       -   sparse matrix format:
+                            * 0 for dense matrix
+                            * 1 for sparse matrix
+            SparseUpper -   which triangle of SparseAC stores matrix  -  upper  or
+                            lower one (for dense matrices this  parameter  is  not
+                            actual).
+            B           -   linear term, array[N]
+            BndL        -   lower bound, array[N]
+            BndU        -   upper bound, array[N]
+            S           -   scale vector, array[NC]:
+                            * I-th element contains scale of I-th variable,
+                            * SC[I]>0
+            XOrigin     -   origin term, array[NC]. Can be zero.
+            N           -   number of variables in the  original  formulation  (no
+                            slack variables).
+            CLEIC       -   dense linear equality/inequality constraints. Equality
+                            constraints come first.
+            NEC, NIC    -   number of dense equality/inequality constraints.
+            SCLEIC      -   sparse linear equality/inequality constraints. Equality
+                            constraints come first.
+            SNEC, SNIC  -   number of sparse equality/inequality constraints.
+            RenormLC    -   whether constraints should be renormalized (recommended)
+                            or used "as is".
+            Settings    -   QPDENSEAULSettings object initialized by one of the initialization
+                            functions.
+            State       -   object which stores temporaries
+            XS          -   initial point, array[NC]
+            
+            
+        OUTPUT PARAMETERS:
+            XS          -   last point
+            TerminationType-termination type:
+                            *
+                            *
+                            *
+
+          -- ALGLIB --
+             Copyright 2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void qpdenseauloptimize(cqmodels.convexquadraticmodel a,
+            sparse.sparsematrix sparsea,
+            int akind,
+            bool sparseaupper,
+            double[] b,
+            double[] bndl,
+            double[] bndu,
+            double[] s,
+            double[] xorigin,
+            int nn,
+            double[,] cleic,
+            int dnec,
+            int dnic,
+            sparse.sparsematrix scleic,
+            int snec,
+            int snic,
+            bool renormlc,
+            qpdenseaulsettings settings,
+            qpdenseaulbuffers state,
+            ref double[] xs,
+            ref int terminationtype,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            double vv = 0;
+            double rho = 0;
+            double epsx = 0;
+            int outeridx = 0;
+            int nmain = 0;
+            int nslack = 0;
+            int ntotal = 0;
+            int ktotal = 0;
+            double maxrho = 0;
+            double feaserr = 0;
+            double feaserrprev = 0;
+            double requestedfeasdecrease = 0;
+            int goodcounter = 0;
+            int stagnationcounter = 0;
+            int nectotal = 0;
+            int nictotal = 0;
+            int nicwork = 0;
+            int kwork = 0;
+            int nwork = 0;
+            bool allowwseviction = new bool();
+            bool workingsetextended = new bool();
+            int i_ = 0;
+
+            terminationtype = 0;
+
+            nmain = nn;
+            nslack = dnic+snic;
+            ntotal = nmain+nslack;
+            nectotal = dnec+snec;
+            nictotal = dnic+snic;
+            ktotal = dnec+dnic+snec+snic;
+            rho = settings.rho;
+            epsx = settings.epsx;
+            requestedfeasdecrease = 0.33;
+            maxrho = 1.0E12;
+            if( (double)(epsx)<=(double)(0) )
+            {
+                epsx = 1.0E-9;
+            }
+            
+            //
+            // Integrity checks
+            //
+            if( snec+snic>0 )
+            {
+                alglib.ap.assert(scleic.matrixtype==1, "QPDENSEAULOptimize: unexpected sparse matrix format");
+                alglib.ap.assert(scleic.m==snec+snic, "QPDENSEAULOptimize: unexpected sparse matrix size");
+                alglib.ap.assert(scleic.n==nmain+1, "QPDENSEAULOptimize: unexpected sparse matrix size");
+            }
+            
+            //
+            // Prepare
+            //
+            state.repinneriterationscount = 0;
+            state.repouteriterationscount = 0;
+            state.repncholesky = 0;
+            state.repnmv = 0;
+            state.repnwrkchanges = 0;
+            state.repnwrk0 = 0;
+            state.repnwrk1 = 0;
+            state.repnwrkf = 0;
+            terminationtype = 0;
+            apserv.rvectorsetlengthatleast(ref state.nulc, ktotal, _params);
+            apserv.rvectorsetlengthatleast(ref state.nulcest, ktotal, _params);
+            apserv.rvectorsetlengthatleast(ref state.exb, ntotal, _params);
+            apserv.rvectorsetlengthatleast(ref state.exxc, ntotal, _params);
+            apserv.rvectorsetlengthatleast(ref state.exxorigin, ntotal, _params);
+            apserv.rvectorsetlengthatleast(ref state.exbndl, ntotal, _params);
+            apserv.rvectorsetlengthatleast(ref state.exbndu, ntotal, _params);
+            apserv.rvectorsetlengthatleast(ref state.exscale, ntotal, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmp0, ntotal, _params);
+            apserv.rvectorsetlengthatleast(ref state.nicerr, nictotal, _params);
+            apserv.ivectorsetlengthatleast(ref state.nicnact, nictotal, _params);
+            
+            //
+            // Prepare scaled/shifted model in dense format - input parameters
+            // are converted and stored in State.SclSftA/B/HasBndL/HasBndU/BndL/BndU/CLEIC/XC
+            //
+            scaleshiftoriginalproblem(a, sparsea, akind, sparseaupper, b, bndl, bndu, s, xorigin, nmain, cleic, dnec, dnic, scleic, snec, snic, renormlc, state, xs, _params);
+            
+            //
+            // Normalize model in such way that norm(A)~1 (very roughly)
+            //
+            // We have two lower bounds for sigma_max(A):
+            // * first estimate is provided by Frobenius norm, it is equal to ANorm/NMain
+            // * second estimate is provided by max(CAC)
+            //
+            // We select largest one of these estimates, because using just one
+            // of them is prone to different failure modes. Then, we divide A and B
+            // by this estimate.
+            //
+            normalizequadraticterm(state.sclsfta, state.sclsftb, nmain, state.sclsftcleic, nectotal, nictotal, renormlc, ref state.tmp2, _params);
+            
+            //
+            // Select working set of inequality constraints.
+            //
+            // Although it is possible to process all inequality constraints
+            // at once, in one large batch, some QP problems have NIC>>N constraints,
+            // but only minor fraction of them is inactive in the solution.
+            //
+            // Because algorithm running time is O((N+NEC+NIC)^3), we can
+            // save a lot of time if we process only those inequality constraints
+            // which need activation. Generally, NEC<N, and only O(N) inequality
+            // constraints are active in the solution.
+            //
+            // We can do so by solving problem first without general inequality
+            // constraints at all (box and general equality constraints are added),
+            // and by iteratively adding more and more inequality constraints in
+            // order to get feasible solution. Such set of inequality constraints
+            // is called "working set".
+            //
+            // NOTE: such approach works reliably only for convex QP problems; non-convex
+            //       QP problem can be unbounded when some constraints are dropped.
+            //
+            // NOTE: we can also remove some constraints from working set, but eviction
+            //       can be performed only limited amount of times (at most once); if
+            //       constraint is added to working set second time, it is never removed.
+            //
+            // NOTE: we do not perform constraint eviction on non-convex problems
+            //
+            selectinitialworkingset(state.sclsfta, nmain, state.sclsftcleic, nectotal, nictotal, ref state.tmp0, ref state.tmp2, ref nicwork, ref allowwseviction, _params);
+            kwork = nectotal+nicwork;
+            nwork = nmain+nicwork;
+            state.repnwrk0 = nicwork;
+            for(i=0; i<=nicwork-1; i++)
+            {
+                state.nicnact[i] = 1;
+            }
+            for(i=nicwork; i<=nictotal-1; i++)
+            {
+                state.nicnact[i] = 0;
+            }
+            
+            //
+            // Perform outer iteration
+            //
+            for(i=0; i<=ktotal-1; i++)
+            {
+                state.nulc[i] = 0;
+            }
+            for(i=0; i<=ntotal-1; i++)
+            {
+                state.exscale[i] = 1.0;
+                state.exxorigin[i] = 0.0;
+            }
+            generateexinitialpoint(state.sclsftxc, nmain, nslack, state.exxc, _params);
+            goodcounter = 0;
+            stagnationcounter = 0;
+            feaserr = math.maxrealnumber;
+            for(outeridx=0; outeridx<=settings.outerits-1; outeridx++)
+            {
+                
+                //
+                // Repeat loop until working set stabilizes.
+                //
+                do
+                {
+                    
+                    //
+                    // Preallocate space for ExA and for QQP solver; we do not allocate
+                    // array[NTotal,NTotal] from the start because NTotal can be much
+                    // larger than NMain for problems with large amount of inequality
+                    // constraints, and we usually need NWork=O(NMain).
+                    //
+                    // NOTE: for the sake of simplicity, 1-dimensional arrays were
+                    //       preallocated to the maximum size required (NTotal).
+                    //
+                    if( alglib.ap.rows(state.exa)<nwork || alglib.ap.cols(state.exa)<nwork )
+                    {
+                        i = nwork+nwork/3+1;
+                        apserv.rmatrixsetlengthatleast(ref state.exa, i, i, _params);
+                    }
+                    qqpsolver.qqppreallocategrowdense(state.qqpbuf, nwork, i, _params);
+                    
+                    //
+                    // Generate penalized quadratic model
+                    //
+                    generateexmodel(state.sclsfta, state.sclsftb, nmain, state.sclsftbndl, state.sclsfthasbndl, state.sclsftbndu, state.sclsfthasbndu, state.sclsftcleic, nectotal, nicwork, state.nulc, rho, state.exa, state.exb, state.exbndl, state.exbndu, ref state.tmp2, _params);
+                    
+                    //
+                    // Solve extended QP problem subject to current working set of general
+                    // inequality constraints.
+                    //
+                    qqpsolver.qqploaddefaults(nwork, state.qqpsettingsuser, _params);
+                    state.qqpsettingsuser.maxouterits = 50;
+                    state.qqpsettingsuser.epsg = 0.0;
+                    state.qqpsettingsuser.epsf = 0.0;
+                    state.qqpsettingsuser.epsx = 0.01*epsx;
+                    state.qqpsettingsuser.cnphase = true;
+                    qqpsolver.qqpoptimize(state.dummycqm, state.dummysparse, state.exa, 2, true, state.exb, state.exbndl, state.exbndu, state.exscale, state.exxorigin, nwork, state.qqpsettingsuser, state.qqpbuf, state.exxc, ref k, _params);
+                    state.repncholesky = state.repncholesky+state.qqpbuf.repncholesky;
+                    
+                    //
+                    // Evaluate violation of constraints
+                    //
+                    for(i=0; i<=nictotal-1; i++)
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=nmain-1;i_++)
+                        {
+                            v += state.sclsftcleic[nectotal+i,i_]*state.exxc[i_];
+                        }
+                        v = v-state.sclsftcleic[nectotal+i,nmain];
+                        state.nicerr[i] = v;
+                    }
+                    
+                    //
+                    // Working set expansion:
+                    // * select limited amount of most violated constraints
+                    // * perform permutation of non-work constraints such that
+                    //   candidate constraint is first the list (update XC and NuLC)
+                    // * increase working set size by 1
+                    // * increase activation count for new constraint by 1 (this count
+                    //   is used later by working set eviction phase)
+                    // * repeat
+                    //
+                    // NOTE: we use selection sort algorithm because its O(NAdded*NWork) cost
+                    //       is still comparable to the cost of constraints evaluation
+                    //
+                    workingsetextended = false;
+                    i = 0;
+                    while( (double)(i)<(double)(1+expansionratio*nmain) && nicwork<nictotal )
+                    {
+                        
+                        //
+                        // Select most violated constraint
+                        //
+                        k = nicwork;
+                        for(j=nicwork; j<=nictotal-1; j++)
+                        {
+                            if( (double)(state.nicerr[j])>(double)(state.nicerr[k]) )
+                            {
+                                k = j;
+                            }
+                        }
+                        
+                        //
+                        // If violation is positive, add it
+                        //
+                        if( (double)(state.nicerr[k])>(double)(0) )
+                        {
+                            apserv.swaprows(state.sclsftcleic, nectotal+nicwork, nectotal+k, -1, _params);
+                            apserv.swapelements(state.nicerr, nicwork, k, _params);
+                            apserv.swapelementsi(state.nicnact, nicwork, k, _params);
+                            state.exxc[nmain+nicwork] = 0.0;
+                            state.nulc[nectotal+nicwork] = 0.0;
+                            state.nicnact[nicwork] = state.nicnact[nicwork]+1;
+                            apserv.inc(ref nicwork, _params);
+                            apserv.inc(ref nwork, _params);
+                            apserv.inc(ref kwork, _params);
+                            apserv.inc(ref i, _params);
+                            workingsetextended = true;
+                        }
+                        else
+                        {
+                            break;
+                        }
+                    }
+                    
+                    //
+                    // Working set eviction:
+                    // * select constraints which are (1) far away from the
+                    //   boundary, AND (2) has less than two activation attempts
+                    //   (if constraint is regularly activated/deactivated, we keep
+                    //   it in the working set no matter what)
+                    // * remove such constraints from the working set one by one
+                    //
+                    if( allowwseviction )
+                    {
+                        for(k=nicwork-1; k>=0; k--)
+                        {
+                            if( (double)(state.nicerr[k])<(double)(evictionlevel) && state.nicnact[k]<=1 )
+                            {
+                                apserv.swaprows(state.sclsftcleic, nectotal+nicwork-1, nectotal+k, -1, _params);
+                                apserv.swapelements(state.nicerr, nicwork-1, k, _params);
+                                apserv.swapelementsi(state.nicnact, nicwork-1, k, _params);
+                                apserv.swapelements(state.exxc, nmain+nicwork-1, nmain+k, _params);
+                                apserv.swapelements(state.nulc, nectotal+nicwork-1, nectotal+k, _params);
+                                apserv.dec(ref nicwork, _params);
+                                apserv.dec(ref nwork, _params);
+                                apserv.dec(ref kwork, _params);
+                            }
+                        }
+                    }
+                    
+                    //
+                    // Report working set statistics
+                    //
+                    if( state.repnwrk1==0 )
+                    {
+                        state.repnwrk1 = nicwork;
+                    }
+                    state.repnwrkf = nicwork;
+                    if( workingsetextended )
+                    {
+                        apserv.inc(ref state.repnwrkchanges, _params);
+                    }
+                }
+                while( workingsetextended );
+                
+                //
+                // Estimate Lagrange multipliers using alternative algorithm
+                //
+                for(i_=0; i_<=kwork-1;i_++)
+                {
+                    state.nulcest[i_] = state.nulc[i_];
+                }
+                updatelagrangemultipliers(state.sclsfta, state.sclsftb, nmain, state.sclsftbndl, state.sclsfthasbndl, state.sclsftbndu, state.sclsfthasbndu, state.sclsftcleic, nectotal, nicwork, state.exxc, state.nulcest, state, _params);
+                
+                //
+                // Update XC and Lagrange multipliers
+                //
+                feaserrprev = feaserr;
+                feaserr = 0;
+                for(i=0; i<=kwork-1; i++)
+                {
+                    
+                    //
+                    // Calculate I-th feasibility error in V using formula for distance
+                    // between point and line (here we calculate actual distance between
+                    // XN and hyperplane Ci'*XN=Bi, which is different from error Ci'*XN-Bi).
+                    //
+                    v = 0;
+                    vv = 0;
+                    for(j=0; j<=nmain-1; j++)
+                    {
+                        v = v+state.sclsftcleic[i,j]*state.exxc[j];
+                        vv = vv+math.sqr(state.sclsftcleic[i,j]);
+                    }
+                    if( i>=nectotal )
+                    {
+                        v = v+state.exxc[nmain+(i-nectotal)];
+                        vv = vv+math.sqr(1);
+                    }
+                    v = v-state.sclsftcleic[i,nmain];
+                    vv = apserv.coalesce(vv, 1, _params);
+                    v = v/Math.Sqrt(vv);
+                    
+                    //
+                    // Calculate magnitude of Lagrangian update (and Lagrangian parameters themselves)
+                    //
+                    feaserr = feaserr+math.sqr(v);
+                    state.nulc[i] = state.nulcest[i];
+                }
+                feaserr = Math.Sqrt(feaserr);
+                if( (double)(feaserr)<(double)(epsx) )
+                {
+                    apserv.inc(ref goodcounter, _params);
+                }
+                else
+                {
+                    goodcounter = 0;
+                }
+                if( (double)(feaserr)>(double)(feaserrprev*requestedfeasdecrease) )
+                {
+                    apserv.inc(ref stagnationcounter, _params);
+                }
+                else
+                {
+                    stagnationcounter = 0;
+                }
+                if( goodcounter>=2 )
+                {
+                    break;
+                }
+                if( stagnationcounter>=2 )
+                {
+                    rho = Math.Min(rho*10.0, maxrho);
+                }
+                else
+                {
+                    rho = Math.Min(rho*1.41, maxrho);
+                }
+            }
+            
+            //
+            // Unpack results.
+            //
+            // Add XOrigin to XC and make sure that boundary constraints are
+            // satisfied.
+            //
+            for(i=0; i<=nmain-1; i++)
+            {
+                
+                //
+                // Unscale/unshift
+                //
+                xs[i] = s[i]*state.exxc[i]+xorigin[i];
+                
+                //
+                // Make sure that point is feasible w.r.t. box constraints.
+                // Enforce box constraints which were active in the scaled/shifted solution.
+                //
+                if( state.sclsfthasbndl[i] )
+                {
+                    if( (double)(xs[i])<(double)(bndl[i]) )
+                    {
+                        xs[i] = bndl[i];
+                    }
+                    if( (double)(state.exxc[i])==(double)(state.sclsftbndl[i]) )
+                    {
+                        xs[i] = bndl[i];
+                    }
+                }
+                if( state.sclsfthasbndu[i] )
+                {
+                    if( (double)(xs[i])>(double)(bndu[i]) )
+                    {
+                        xs[i] = bndu[i];
+                    }
+                    if( (double)(state.exxc[i])==(double)(state.sclsftbndu[i]) )
+                    {
+                        xs[i] = bndu[i];
+                    }
+                }
+            }
+            terminationtype = 2;
+        }
+
+
+        /*************************************************************************
+        This function generates box-constrained QP problem, which is penalized and
+        augmented formulation of original linearly constrained problem
+
+          -- ALGLIB --
+             Copyright 23.02.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void generateexmodel(double[,] sclsfta,
+            double[] sclsftb,
+            int nmain,
+            double[] sclsftbndl,
+            bool[] sclsfthasbndl,
+            double[] sclsftbndu,
+            bool[] sclsfthasbndu,
+            double[,] sclsftcleic,
+            int sclsftnec,
+            int sclsftnic,
+            double[] nulc,
+            double rho,
+            double[,] exa,
+            double[] exb,
+            double[] exbndl,
+            double[] exbndu,
+            ref double[,] tmp2,
+            alglib.xparams _params)
+        {
+            int nslack = 0;
+            int ntotal = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int i_ = 0;
+
+            nslack = sclsftnic;
+            ntotal = nmain+nslack;
+            
+            //
+            // Integrity check for properly preallocated storage
+            //
+            alglib.ap.assert(alglib.ap.rows(exa)>=ntotal && alglib.ap.cols(exa)>=ntotal, "QPDenseAUL.GenerateExModel - integrity check failed");
+            alglib.ap.assert((alglib.ap.len(exb)>=ntotal && alglib.ap.len(exbndl)>=ntotal) && alglib.ap.len(exbndu)>=ntotal, "QPDenseAUL.GenerateExModel - integrity check failed");
+            
+            //
+            // Primary quadratic term
+            //
+            for(i=0; i<=ntotal-1; i++)
+            {
+                for(j=i; j<=ntotal-1; j++)
+                {
+                    exa[i,j] = 0;
+                }
+            }
+            for(i=0; i<=nmain-1; i++)
+            {
+                for(j=i; j<=nmain-1; j++)
+                {
+                    exa[i,j] = sclsfta[i,j];
+                }
+            }
+            
+            //
+            // Primary linear term
+            //
+            for(i=0; i<=ntotal-1; i++)
+            {
+                exb[i] = 0;
+            }
+            for(i=0; i<=nmain-1; i++)
+            {
+                exb[i] = sclsftb[i];
+            }
+            
+            //
+            // Box constraints - move primary, add slack
+            //
+            for(i=0; i<=nmain-1; i++)
+            {
+                if( sclsfthasbndl[i] )
+                {
+                    exbndl[i] = sclsftbndl[i];
+                }
+                else
+                {
+                    exbndl[i] = Double.NegativeInfinity;
+                }
+                if( sclsfthasbndu[i] )
+                {
+                    exbndu[i] = sclsftbndu[i];
+                }
+                else
+                {
+                    exbndu[i] = Double.PositiveInfinity;
+                }
+            }
+            for(i=nmain; i<=ntotal-1; i++)
+            {
+                exbndl[i] = 0;
+                exbndu[i] = Double.PositiveInfinity;
+            }
+            
+            //
+            // Handle equality constraints:
+            // * modify quadratic term
+            // * modify linear term
+            // * add Lagrangian term
+            //
+            apserv.rmatrixsetlengthatleast(ref tmp2, sclsftnec+sclsftnic, ntotal, _params);
+            for(i=0; i<=sclsftnec+sclsftnic-1; i++)
+            {
+                
+                //
+                // Given constraint row ci and right hand side ri,
+                // I-th quadratic constraint adds penalty term
+                //
+                //     0.5*Rho*(ci'*x-ri)^2 =
+                //     = 0.5*Rho*(ci'*x-ri)^T*(ci'*x-ri) =
+                //     = 0.5*Rho*(x'*ci-ri')*(ci'*x-ri) =
+                //     = 0.5*Rho*(x'*ci*ci'*x - ri'*ci'*x - x'*ci*ri + ri'*ri )
+                //     = 0.5*Rho*(x'*(ci*ci')*x - 2*ri*(ci'*x) + ri^2 )
+                //
+                // Thus, quadratic term is updated by
+                //
+                //     0.5*Rho*(ci*ci')
+                //
+                // (with actual update to ExA being performed without 0.5
+                // multiplier because entire matrix is post-multipliead by 0.5)
+                // and linear term receives update
+                //
+                //     -Rho*ri*ci
+                //
+                // Similaryly, lagrangian term is -NUi*(ci'*x-ri),
+                // so linear term is updated by
+                //
+                //     -NUi*ci
+                //
+                // Because our model does not take into account constant term,
+                // we calculate just quadratic and linear terms.
+                //
+                for(i_=0; i_<=nmain-1;i_++)
+                {
+                    tmp2[i,i_] = sclsftcleic[i,i_];
+                }
+                for(j=nmain; j<=ntotal-1; j++)
+                {
+                    tmp2[i,j] = 0;
+                }
+                if( i>=sclsftnec )
+                {
+                    tmp2[i,nmain+i-sclsftnec] = 1.0;
+                }
+                v = -(rho*sclsftcleic[i,nmain]);
+                for(i_=0; i_<=ntotal-1;i_++)
+                {
+                    exb[i_] = exb[i_] + v*tmp2[i,i_];
+                }
+                v = -nulc[i];
+                for(i_=0; i_<=ntotal-1;i_++)
+                {
+                    exb[i_] = exb[i_] + v*tmp2[i,i_];
+                }
+            }
+            ablas.rmatrixsyrk(ntotal, sclsftnec+sclsftnic, rho, tmp2, 0, 0, 2, 1.0, exa, 0, 0, true, _params);
+        }
+
+
+        /*************************************************************************
+        This function generates initial point for  "extended"  box-constrained  QP
+        problem.
+
+          -- ALGLIB --
+             Copyright 23.02.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void generateexinitialpoint(double[] sclsftxc,
+            int nmain,
+            int nslack,
+            double[] exxc,
+            alglib.xparams _params)
+        {
+            int ntotal = 0;
+            int i = 0;
+
+            ntotal = nmain+nslack;
+            for(i=0; i<=ntotal-1; i++)
+            {
+                exxc[i] = 0;
+            }
+            for(i=0; i<=nmain-1; i++)
+            {
+                exxc[i] = sclsftxc[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function estimates Lagrange multipliers for scaled-shifted QP problem
+        (here "scaled-shifted"  means  that we  performed  variable  scaling   and
+        subtracted origin) given by quadratic term A, linear term B, box constraints
+        and linear constraint matrix.
+
+        It is assumed that all linear constraints are equality  ones,  with  first
+        NEC ones being constraints without slack  variables,  and  next  NIC  ones
+        having slack variables. The only inequality constraints we  have  are  box
+        ones, with first NMain ones being "general" box constraints, and next  NIC
+        ones being non-negativity constraints (not specified explicitly).
+
+        We also make use of the current point XC, which is used to determine active
+        box constraints.
+
+        Actual QP problem size is NMain+NIC, but   some  parameters   have   lower
+        dimensionality.
+
+        Parameters sizes are:
+        * A is assumed to be array[NMain,NMain]
+        * B is assumed to be array[NMain]
+        * BndL, BndU are array[NMain]
+        * CLEIC is array[NEC+NIC,NMain+1] (last item in a row containts right part)
+        * ExXC is array[NMain+NIC], holds current point
+        * NuLCEst is array[NEC+NIC], holds initial values of Lagrange coeffs
+
+        On exit NuLCEst is updated with new estimate of Lagrange multipliers.
+
+          -- ALGLIB --
+             Copyright 23.02.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void updatelagrangemultipliers(double[,] sclsfta,
+            double[] sclsftb,
+            int nmain,
+            double[] sclsftbndl,
+            bool[] sclsfthasbndl,
+            double[] sclsftbndu,
+            bool[] sclsfthasbndu,
+            double[,] sclsftcleic,
+            int sclsftnec,
+            int sclsftnic,
+            double[] exxc,
+            double[] nulcest,
+            qpdenseaulbuffers buffers,
+            alglib.xparams _params)
+        {
+            int nslack = 0;
+            int ntotal = 0;
+            int ktotal = 0;
+            int nqrrows = 0;
+            int nqrcols = 0;
+            int i = 0;
+            int j = 0;
+            double lambdareg = 0;
+            double mxdiag = 0;
+            double v = 0;
+            bool isactive = new bool();
+            int i_ = 0;
+
+            nslack = sclsftnic;
+            ntotal = nmain+nslack;
+            ktotal = sclsftnec+sclsftnic;
+            
+            //
+            // Given current point ExXC, we can determine active and inactive
+            // constraints. After we drop inactive inequality constraints, we
+            // have equality-only constrained QP problem, with mix of general
+            // linear equality constraints and "simple" constraints Xi=Ci.
+            //
+            // Problem min(0.5*x'*A*x + b'*x) s.t. C*x=d (general linear
+            // constraints) can be solved by explicitly writing out Lagrange
+            // equations:
+            //
+            //     [ A  C' ] [ X ]   [ -b]
+            //     [       ] [   ] = [   ]
+            //     [ C     ] [ L ]   [ d ]
+            //
+            // or
+            //
+            //         [ X ]
+            //     A1* [   ] = b1
+            //         [ L ]
+            //
+            // where X stands for solution itself, and L stands for Lagrange
+            // multipliers. It can be easily solved with direct linear solver.
+            // However, such formulation does not account for "simple" equality
+            // constraints on variables. It is possible to include "simple"
+            // constraints into "general" ones (i.e. append (0 ... 0 -1 0 ... 0)'
+            // to the constraint matrix), but it will increase problem
+            // size.
+            //
+            // Another approach is to use initial values of X and L (X0 and L0)
+            // as starting point, and to solve for "offset" from (X0, L0):
+            //
+            //        [ X0+X1 ]
+            //     A1*[       ] = b1
+            //        [ L0+L1 ]
+            //
+            // or 
+            //
+            //        [ X1 ]           [ X0 ]
+            //     A1*[    ] = b1 - A1*[    ]
+            //        [ L1 ]           [ L0 ]
+            //
+            // In such formulation components of X1 which correspond to active
+            // constraints on variables are "frozen" at value 0 (because we have
+            // equality constraint, offset from constrained value have to be zero).
+            //
+            // Thus, we can rewrite corresponding columns of A1 with zeros - and
+            // use this space to store (0 ... 0 -1 0 ... 0)', which is used to
+            // account for Lagrange multipliers for "simple" constraints.
+            //
+            nqrcols = ntotal+ktotal;
+            nqrrows = nqrcols;
+            apserv.rvectorsetlengthatleast(ref buffers.qrsv0, nqrcols, _params);
+            apserv.rvectorsetlengthatleast(ref buffers.qrsvx1, nqrcols, _params);
+            for(i=0; i<=ntotal-1; i++)
+            {
+                buffers.qrsv0[i] = exxc[i];
+            }
+            for(i=0; i<=ktotal-1; i++)
+            {
+                buffers.qrsv0[ntotal+i] = nulcest[i];
+            }
+            apserv.rmatrixsetlengthatleast(ref buffers.qrkkt, nqrcols+nqrcols, nqrcols+1, _params);
+            apserv.rvectorsetlengthatleast(ref buffers.qrrightpart, nqrcols+nqrcols, _params);
+            lambdareg = 1.0E-8;
+            while( true )
+            {
+                
+                //
+                // Initialize matrix A1 and right part b1 with zeros
+                //
+                for(i=0; i<=alglib.ap.rows(buffers.qrkkt)-1; i++)
+                {
+                    for(j=0; j<=alglib.ap.cols(buffers.qrkkt)-1; j++)
+                    {
+                        buffers.qrkkt[i,j] = 0;
+                    }
+                    buffers.qrrightpart[i] = 0;
+                }
+                
+                //
+                // Append quadratic term (note: we implicitly add NSlack zeros to
+                // A and b).
+                //
+                mxdiag = 0;
+                for(i=0; i<=nmain-1; i++)
+                {
+                    for(j=0; j<=nmain-1; j++)
+                    {
+                        buffers.qrkkt[i,j] = sclsfta[i,j];
+                    }
+                    buffers.qrrightpart[i] = -sclsftb[i];
+                    mxdiag = Math.Max(mxdiag, Math.Abs(sclsfta[i,i]));
+                }
+                mxdiag = apserv.coalesce(mxdiag, 1, _params);
+                
+                //
+                // Append general linear constraints
+                //
+                for(i=0; i<=ktotal-1; i++)
+                {
+                    for(j=0; j<=nmain-1; j++)
+                    {
+                        buffers.qrkkt[ntotal+i,j] = -sclsftcleic[i,j];
+                        buffers.qrkkt[j,ntotal+i] = -sclsftcleic[i,j];
+                    }
+                    if( i>=sclsftnec )
+                    {
+                        buffers.qrkkt[ntotal+i,nmain+(i-sclsftnec)] = -1;
+                        buffers.qrkkt[nmain+(i-sclsftnec),ntotal+i] = -1;
+                    }
+                    buffers.qrrightpart[ntotal+i] = -sclsftcleic[i,nmain];
+                }
+                
+                //
+                // Append regularizer to the bottom of the matrix
+                // (it will be factored in during QR decomposition)
+                //
+                if( (double)(lambdareg)>(double)(0) )
+                {
+                    nqrrows = nqrcols+nqrcols;
+                    for(i=0; i<=nqrcols-1; i++)
+                    {
+                        buffers.qrkkt[nqrcols+i,i] = lambdareg*mxdiag;
+                    }
+                }
+                
+                //
+                // Subtract reference point (X0,L0) from the system
+                //
+                for(i=0; i<=nqrcols-1; i++)
+                {
+                    v = 0.0;
+                    for(i_=0; i_<=nqrcols-1;i_++)
+                    {
+                        v += buffers.qrkkt[i,i_]*buffers.qrsv0[i_];
+                    }
+                    buffers.qrrightpart[i] = buffers.qrrightpart[i]-v;
+                }
+                
+                //
+                // Handle active "simple" equality constraints
+                //
+                for(i=0; i<=ntotal-1; i++)
+                {
+                    isactive = false;
+                    if( i<nmain && ((sclsfthasbndl[i] && (double)(exxc[i])==(double)(sclsftbndl[i])) || (sclsfthasbndu[i] && (double)(exxc[i])==(double)(sclsftbndu[i]))) )
+                    {
+                        isactive = true;
+                    }
+                    if( i>=nmain && (double)(exxc[i])==(double)(0.0) )
+                    {
+                        isactive = true;
+                    }
+                    if( !isactive )
+                    {
+                        continue;
+                    }
+                    for(j=0; j<=nqrrows-1; j++)
+                    {
+                        buffers.qrkkt[j,i] = 0;
+                    }
+                    buffers.qrkkt[i,i] = -1;
+                }
+                
+                //
+                // Solve via QR decomposition:
+                // * append right part to the system matrix
+                // * perform QR decomposition of the extended matrix (right part is implicitly
+                //   multiplied by Q during decomposition; believe me, it works!)
+                // * check condition number, increase regularization value if necessary and retry
+                // * solve triangular system, break iteration
+                //
+                for(i=0; i<=nqrrows-1; i++)
+                {
+                    buffers.qrkkt[i,nqrcols] = buffers.qrrightpart[i];
+                }
+                ortfac.rmatrixqr(ref buffers.qrkkt, nqrrows, nqrcols+1, ref buffers.qrtau, _params);
+                if( (double)(rcond.rmatrixtrrcond1(buffers.qrkkt, nqrcols, true, false, _params))<=(double)(1000*math.machineepsilon) )
+                {
+                    lambdareg = apserv.coalesce(10*lambdareg, 1.0E-13, _params);
+                    continue;
+                }
+                for(i=nqrcols-1; i>=0; i--)
+                {
+                    v = buffers.qrkkt[i,nqrcols];
+                    for(j=i+1; j<=nqrcols-1; j++)
+                    {
+                        v = v-buffers.qrkkt[i,j]*buffers.qrsvx1[j];
+                    }
+                    buffers.qrsvx1[i] = v/buffers.qrkkt[i,i];
+                }
+                break;
+            }
+            
+            //
+            // Update Lagrange coefficients
+            //
+            for(i=0; i<=ktotal-1; i++)
+            {
+                nulcest[i] = buffers.qrsv0[ntotal+i]+buffers.qrsvx1[ntotal+i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function generates scaled (by S) and shifted (by XC) reformulation of
+        the original problem.
+
+        INPUT PARAMETERS:
+            DenseA      -   for dense problems (AKind=0), A-term of CQM object
+                            contains system matrix. Other terms are unspecified
+                            and should not be referenced.
+            SparseA     -   for sparse problems (AKind=1), CRS format
+            AKind       -   sparse matrix format:
+                            * 0 for dense matrix
+                            * 1 for sparse matrix
+            SparseUpper -   which triangle of SparseAC stores matrix  -  upper  or
+                            lower one (for dense matrices this  parameter  is  not
+                            actual).
+            B           -   linear term, array[N]
+            BndL        -   lower bound, array[N]
+            BndU        -   upper bound, array[N]
+            S           -   scale vector, array[NC]:
+                            * I-th element contains scale of I-th variable,
+                            * SC[I]>0
+            XOrigin     -   origin term, array[NC]. Can be zero.
+            N           -   number of variables in the  original  formulation  (no
+                            slack variables).
+            CLEIC       -   dense linear equality/inequality constraints. Equality
+                            constraints come first.
+            NEC, NIC    -   number of dense equality/inequality constraints.
+            SCLEIC      -   sparse linear equality/inequality constraints. Equality
+                            constraints come first.
+            SNEC, SNIC  -   number of sparse equality/inequality constraints.
+            RenormLC    -   whether constraints should be renormalized (recommended)
+                            or used "as is".
+            Settings    -   QPDENSEAULSettings object initialized by one of the initialization
+                            functions.
+            State       -   object which stores temporaries
+            XS          -   initial point, array[NC]
+
+            
+        On output, following fields of the State structure are modified:
+        * SclSftA       -   array[NMain,NMain], quadratic term, both triangles
+        * SclSftB       -   array[NMain], linear term
+        * SclSftXC      -   array[NMain], initial point
+        * SclSftHasBndL,
+          SclSftHasBndU,
+          SclSftBndL,
+          SclSftBndU    -   array[NMain], lower/upper bounds
+        * SclSftCLEIC   -   array[KTotal,NMain+1], general linear constraints
+
+        NOTE: State.Tmp2 is used to store temporary array[NMain,NMain]
+
+          -- ALGLIB --
+             Copyright 01.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void scaleshiftoriginalproblem(cqmodels.convexquadraticmodel a,
+            sparse.sparsematrix sparsea,
+            int akind,
+            bool sparseaupper,
+            double[] b,
+            double[] bndl,
+            double[] bndu,
+            double[] s,
+            double[] xorigin,
+            int nmain,
+            double[,] cleic,
+            int dnec,
+            int dnic,
+            sparse.sparsematrix scleic,
+            int snec,
+            int snic,
+            bool renormlc,
+            qpdenseaulbuffers state,
+            double[] xs,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int j0 = 0;
+            int j1 = 0;
+            double v = 0;
+            double vv = 0;
+            int ktotal = 0;
+
+            alglib.ap.assert(akind==0 || akind==1, "QPDENSEAULOptimize: unexpected AKind");
+            ktotal = dnec+dnic+snec+snic;
+            apserv.rmatrixsetlengthatleast(ref state.sclsfta, nmain, nmain, _params);
+            apserv.rvectorsetlengthatleast(ref state.sclsftb, nmain, _params);
+            apserv.rvectorsetlengthatleast(ref state.sclsftxc, nmain, _params);
+            apserv.rvectorsetlengthatleast(ref state.sclsftbndl, nmain, _params);
+            apserv.rvectorsetlengthatleast(ref state.sclsftbndu, nmain, _params);
+            apserv.bvectorsetlengthatleast(ref state.sclsfthasbndl, nmain, _params);
+            apserv.bvectorsetlengthatleast(ref state.sclsfthasbndu, nmain, _params);
+            apserv.rmatrixsetlengthatleast(ref state.sclsftcleic, ktotal, nmain+1, _params);
+            if( akind==0 )
+            {
+                
+                //
+                // Extract dense A and scale
+                //
+                cqmodels.cqmgeta(a, ref state.tmp2, _params);
+                for(i=0; i<=nmain-1; i++)
+                {
+                    for(j=0; j<=nmain-1; j++)
+                    {
+                        state.sclsfta[i,j] = 0;
+                    }
+                }
+                for(i=0; i<=nmain-1; i++)
+                {
+                    for(j=i; j<=nmain-1; j++)
+                    {
+                        v = state.tmp2[i,j]*s[i]*s[j];
+                        state.sclsfta[i,j] = v;
+                        state.sclsfta[j,i] = v;
+                    }
+                }
+            }
+            if( akind==1 )
+            {
+                
+                //
+                // Extract sparse A and scale
+                //
+                alglib.ap.assert(sparsea.matrixtype==1, "QPDENSEAULOptimize: unexpected sparse matrix format");
+                alglib.ap.assert(sparsea.m==nmain, "QPDENSEAULOptimize: unexpected sparse matrix size");
+                alglib.ap.assert(sparsea.n==nmain, "QPDENSEAULOptimize: unexpected sparse matrix size");
+                for(i=0; i<=nmain-1; i++)
+                {
+                    for(j=0; j<=nmain-1; j++)
+                    {
+                        state.sclsfta[i,j] = 0;
+                    }
+                }
+                if( sparseaupper )
+                {
+                    for(i=0; i<=nmain-1; i++)
+                    {
+                        if( sparsea.didx[i]!=sparsea.uidx[i] )
+                        {
+                            state.sclsfta[i,i] = sparsea.vals[sparsea.didx[i]]*s[i]*s[i];
+                        }
+                        j0 = sparsea.uidx[i];
+                        j1 = sparsea.ridx[i+1]-1;
+                        for(j=j0; j<=j1; j++)
+                        {
+                            k = sparsea.idx[j];
+                            v = sparsea.vals[j]*s[i]*s[k];
+                            state.sclsfta[i,k] = v;
+                            state.sclsfta[k,i] = v;
+                        }
+                    }
+                }
+                else
+                {
+                    for(i=0; i<=nmain-1; i++)
+                    {
+                        if( sparsea.didx[i]!=sparsea.uidx[i] )
+                        {
+                            state.sclsfta[i,i] = sparsea.vals[sparsea.didx[i]]*s[i]*s[i];
+                        }
+                        j0 = sparsea.ridx[i];
+                        j1 = sparsea.didx[i]-1;
+                        for(j=j0; j<=j1; j++)
+                        {
+                            k = sparsea.idx[j];
+                            v = sparsea.vals[j]*s[i]*s[k];
+                            state.sclsfta[i,k] = v;
+                            state.sclsfta[k,i] = v;
+                        }
+                    }
+                }
+            }
+            for(i=0; i<=nmain-1; i++)
+            {
+                state.sclsftb[i] = b[i]*s[i];
+            }
+            for(i=0; i<=nmain-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(bndl[i]) || Double.IsNegativeInfinity(bndl[i]), "QPDENSEAULOptimize: integrity check failure (7)");
+                alglib.ap.assert(math.isfinite(bndu[i]) || Double.IsPositiveInfinity(bndu[i]), "QPDENSEAULOptimize: integrity check failure (8)");
+                state.sclsftxc[i] = (xs[i]-xorigin[i])/s[i];
+                state.sclsfthasbndl[i] = math.isfinite(bndl[i]);
+                state.sclsfthasbndu[i] = math.isfinite(bndu[i]);
+                if( (state.sclsfthasbndl[i] && state.sclsfthasbndu[i]) && (double)(bndl[i])==(double)(bndu[i]) )
+                {
+                    
+                    //
+                    // Make sure that SclSftBndL[I]=SclSftBndU[I] bit-to-bit
+                    // even with CRAZY optimizing compiler.
+                    //
+                    state.sclsftbndu[i] = (bndu[i]-xorigin[i])/s[i];
+                    state.sclsftbndl[i] = state.sclsftbndu[i];
+                    continue;
+                }
+                if( state.sclsfthasbndl[i] )
+                {
+                    state.sclsftbndl[i] = (bndl[i]-xorigin[i])/s[i];
+                }
+                if( state.sclsfthasbndu[i] )
+                {
+                    state.sclsftbndu[i] = (bndu[i]-xorigin[i])/s[i];
+                }
+            }
+            for(i=0; i<=ktotal-1; i++)
+            {
+                for(j=0; j<=nmain; j++)
+                {
+                    state.sclsftcleic[i,j] = 0;
+                }
+            }
+            for(i=0; i<=dnec-1; i++)
+            {
+                for(j=0; j<=nmain-1; j++)
+                {
+                    v = cleic[i,j]*s[j];
+                    state.sclsftcleic[i,j] = v;
+                }
+                state.sclsftcleic[i,nmain] = cleic[i,nmain];
+            }
+            for(i=0; i<=dnic-1; i++)
+            {
+                for(j=0; j<=nmain-1; j++)
+                {
+                    v = cleic[dnec+i,j]*s[j];
+                    state.sclsftcleic[dnec+snec+i,j] = v;
+                }
+                state.sclsftcleic[dnec+snec+i,nmain] = cleic[dnec+i,nmain];
+            }
+            for(i=0; i<=snec-1; i++)
+            {
+                
+                //
+                // Because constraints are sparse, everything is a bit tricky -
+                // it is possible that N-th element of the row is zero and not
+                // stored; it is also possible that entire row is empty.
+                //
+                j0 = scleic.ridx[i];
+                j1 = scleic.ridx[i+1]-1;
+                if( j1>=j0 && scleic.idx[j1]==nmain )
+                {
+                    state.sclsftcleic[dnec+i,nmain] = scleic.vals[j1];
+                    j1 = j1-1;
+                }
+                for(j=j0; j<=j1; j++)
+                {
+                    k = scleic.idx[j];
+                    v = scleic.vals[j]*s[k];
+                    state.sclsftcleic[dnec+i,k] = v;
+                }
+            }
+            for(i=0; i<=snic-1; i++)
+            {
+                
+                //
+                // Because constraints are sparse, everything is a bit tricky -
+                // it is possible that N-th element of the row is zero and not
+                // stored; it is also possible that entire row is empty.
+                //
+                j0 = scleic.ridx[snec+i];
+                j1 = scleic.ridx[snec+i+1]-1;
+                if( j1>=j0 && scleic.idx[j1]==nmain )
+                {
+                    state.sclsftcleic[dnec+snec+dnic+i,nmain] = scleic.vals[j1];
+                    j1 = j1-1;
+                }
+                for(j=j0; j<=j1; j++)
+                {
+                    k = scleic.idx[j];
+                    v = scleic.vals[j]*s[k];
+                    state.sclsftcleic[dnec+snec+dnic+i,k] = v;
+                }
+            }
+            if( renormlc && ktotal>0 )
+            {
+                
+                //
+                // Normalize linear constraints in such way that they have unit norm
+                // (after variable scaling)
+                //
+                for(i=0; i<=ktotal-1; i++)
+                {
+                    vv = 0.0;
+                    for(j=0; j<=nmain-1; j++)
+                    {
+                        v = state.sclsftcleic[i,j];
+                        vv = vv+v*v;
+                    }
+                    vv = Math.Sqrt(vv);
+                    if( (double)(vv)>(double)(0) )
+                    {
+                        vv = 1/vv;
+                        for(j=0; j<=nmain; j++)
+                        {
+                            state.sclsftcleic[i,j] = state.sclsftcleic[i,j]*vv;
+                        }
+                    }
+                }
+            }
+            for(i=0; i<=ktotal-1; i++)
+            {
+                
+                //
+                // Apply XOrigin
+                //
+                v = 0.0;
+                for(j=0; j<=nmain-1; j++)
+                {
+                    v = v+state.sclsftcleic[i,j]*(xorigin[j]/s[j]);
+                }
+                state.sclsftcleic[i,nmain] = state.sclsftcleic[i,nmain]-v;
+            }
+        }
+
+
+        /*************************************************************************
+        Normalize model in such way that norm(A)~1 (very roughly)
+
+        We have two lower bounds for sigma_max(A):
+        * first estimate is provided by Frobenius norm, it is equal to ANorm/NMain
+        * second estimate is provided by max(CAC)
+
+        We select largest one of these estimates, because using just one
+        of them is prone to different failure modes. Then, we divide A and B
+        by this estimate.
+
+        INPUT PARAMETERS:
+            A   -       array[N,N], quadratic term, full triangle is given
+            B   -       array[N], linear term
+            N   -       problem size
+            CLEIC-      array[NEC+NIC,N+1], linear equality/inequality constraints
+            NEC -       number of equality constraints
+            NIC -       number of inequality constraints
+            UseCLEIC-   additional normalization of A in such way that CLEIC*A*CLEIC'~1:
+                        * if False, CLEIC is ignored
+                        * if True, CLEIC rows MUST have unit norm (we check it)
+            Tmp2-       additional buffer, possibly preallocated
+            
+        OUTPUT PARAMETERS:
+            A, B - appropriately rescaled
+
+          -- ALGLIB --
+             Copyright 01.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void normalizequadraticterm(double[,] a,
+            double[] b,
+            int n,
+            double[,] cleic,
+            int nec,
+            int nic,
+            bool usecleic,
+            ref double[,] tmp2,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double anorm = 0;
+            double maxcac = 0;
+            double v = 0;
+            double vv = 0;
+            int ktotal = 0;
+            int nmain = 0;
+
+            nmain = n;
+            ktotal = nec+nic;
+            anorm = 0;
+            for(i=0; i<=nmain-1; i++)
+            {
+                for(j=0; j<=nmain-1; j++)
+                {
+                    anorm = anorm+math.sqr(a[i,j]);
+                }
+            }
+            anorm = Math.Sqrt(anorm);
+            if( usecleic && ktotal>0 )
+            {
+                
+                //
+                // Calculate max(|diag(C*A*C')|), where C is constraint matrix
+                //
+                apserv.rmatrixsetlengthatleast(ref tmp2, ktotal, nmain, _params);
+                ablas.rmatrixgemm(ktotal, nmain, nmain, 1.0, cleic, 0, 0, 0, a, 0, 0, 0, 0.0, tmp2, 0, 0, _params);
+                maxcac = 0.0;
+                for(i=0; i<=ktotal-1; i++)
+                {
+                    v = 0;
+                    vv = 0;
+                    for(j=0; j<=nmain-1; j++)
+                    {
+                        v = v+tmp2[i,j]*cleic[i,j];
+                        vv = vv+math.sqr(cleic[i,j]);
+                    }
+                    alglib.ap.assert((double)(Math.Abs(vv-1))<(double)(1.0E-9) || (double)(vv)==(double)(0), "DENSE-AUL: integrity check failed");
+                    maxcac = Math.Max(maxcac, Math.Abs(v));
+                }
+            }
+            else
+            {
+                maxcac = 0;
+            }
+            v = 1/apserv.coalesce(Math.Max(maxcac, anorm/nmain), 1, _params);
+            for(i=0; i<=nmain-1; i++)
+            {
+                for(j=0; j<=nmain-1; j++)
+                {
+                    a[i,j] = a[i,j]*v;
+                }
+            }
+            for(i=0; i<=nmain-1; i++)
+            {
+                b[i] = b[i]*v;
+            }
+        }
+
+
+        /*************************************************************************
+        This function selects initial working set of general inequality constraints
+        for QP problem:
+        * for non-convex QP problems    -   NICWork=NIC is returned
+        * otherwise                     -   NICWork=0 is returned (we have to
+                                            determine working set iteratively)
+
+        INPUT PARAMETERS:
+            A           -   array[NMain], quadratic term, full matrix is stored
+            NMain       -   number of variables in the "original" QP problem
+            CLEIC       -   array[NEC+NIC,NMain+1], constraint matrix
+            NEC         -   number of equality constraints
+            NIC         -   number of inequality constraints
+
+        OUTPUT PARAMETERS:
+            NICWork     -   recommended size of working set; in current version
+                            either all (NICWork=NIC) or none (NICWork=0) constraints
+                            are included.
+            AllowWSEviction-whether problem properties allow eviction of constraints
+                            from working set or not. Non-convex problems do not
+                            allow eviction, convex ones do.
+
+          -- ALGLIB --
+             Copyright 02.10.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void selectinitialworkingset(double[,] a,
+            int nmain,
+            double[,] cleic,
+            int nec,
+            int nic,
+            ref double[] tmp0,
+            ref double[,] tmp2,
+            ref int nicwork,
+            ref bool allowwseviction,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            nicwork = 0;
+            allowwseviction = new bool();
+
+            apserv.rmatrixsetlengthatleast(ref tmp2, nmain, nmain, _params);
+            apserv.rvectorsetlengthatleast(ref tmp0, nmain, _params);
+            for(i=0; i<=nmain-1; i++)
+            {
+                for(j=i; j<=nmain-1; j++)
+                {
+                    tmp2[i,j] = a[i,j];
+                }
+            }
+            if( !trfac.spdmatrixcholeskyrec(ref tmp2, 0, nmain, true, ref tmp0, _params) )
+            {
+                
+                //
+                // Matrix is indefinite.
+                //
+                // We have to select full working set, otherwise algorithm may fail
+                // because problem with reduced working set can be unbounded from below.
+                //
+                nicwork = nic;
+                allowwseviction = false;
+            }
+            else
+            {
+                
+                //
+                // Positive definite matrix.
+                //
+                // We can select zero initial working set and expand it later.
+                //
+                nicwork = 0;
+                allowwseviction = true;
+            }
+        }
+
+
+    }
+    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 : apobject
+        {
+            public int nmain;
+            public int nslack;
+            public double epsg;
+            public double epsf;
+            public double epsx;
+            public int maxits;
+            public bool xrep;
+            public bool drep;
+            public double stpmax;
+            public double diffstep;
+            public sactivesets.sactiveset sas;
+            public double[] s;
+            public int prectype;
+            public double[] diagh;
+            public double[] x;
+            public double f;
+            public double[] g;
+            public bool needf;
+            public bool needfg;
+            public bool xupdated;
+            public bool lsstart;
+            public bool steepestdescentstep;
+            public bool boundedstep;
+            public bool userterminationneeded;
+            public rcommstate rstate;
+            public double[] ugc;
+            public double[] cgc;
+            public double[] xn;
+            public double[] ugn;
+            public double[] cgn;
+            public double[] xp;
+            public double fc;
+            public double fn;
+            public double fp;
+            public double[] d;
+            public double[,] cleic;
+            public int nec;
+            public int nic;
+            public double lastgoodstep;
+            public double lastscaledgoodstep;
+            public double maxscaledgrad;
+            public bool[] hasbndl;
+            public bool[] hasbndu;
+            public double[] bndl;
+            public double[] bndu;
+            public int repinneriterationscount;
+            public int repouteriterationscount;
+            public int repnfev;
+            public int repvaridx;
+            public int repterminationtype;
+            public double repdebugeqerr;
+            public double repdebugfs;
+            public double repdebugff;
+            public double repdebugdx;
+            public int repdebugfeasqpits;
+            public int repdebugfeasgpaits;
+            public double[] xstart;
+            public snnls.snnlssolver solver;
+            public double fbase;
+            public double fm2;
+            public double fm1;
+            public double fp1;
+            public double fp2;
+            public double xm1;
+            public double xp1;
+            public double gm1;
+            public double gp1;
+            public int cidx;
+            public double cval;
+            public double[] tmpprec;
+            public double[] tmp0;
+            public int nfev;
+            public int mcstage;
+            public double stp;
+            public double curstpmax;
+            public double activationstep;
+            public double[] work;
+            public linmin.linminstate lstate;
+            public double trimthreshold;
+            public int nonmonotoniccnt;
+            public double[,] bufyk;
+            public double[,] bufsk;
+            public double[] bufrho;
+            public double[] buftheta;
+            public int bufsize;
+            public double teststep;
+            public int smoothnessguardlevel;
+            public optserv.smoothnessmonitor smonitor;
+            public double[] lastscaleused;
+            public double[] invs;
+            public minbleicstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                sas = new sactivesets.sactiveset();
+                s = new double[0];
+                diagh = new double[0];
+                x = new double[0];
+                g = new double[0];
+                rstate = new rcommstate();
+                ugc = new double[0];
+                cgc = new double[0];
+                xn = new double[0];
+                ugn = new double[0];
+                cgn = new double[0];
+                xp = new double[0];
+                d = new double[0];
+                cleic = new double[0,0];
+                hasbndl = new bool[0];
+                hasbndu = new bool[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                xstart = new double[0];
+                solver = new snnls.snnlssolver();
+                tmpprec = new double[0];
+                tmp0 = new double[0];
+                work = new double[0];
+                lstate = new linmin.linminstate();
+                bufyk = new double[0,0];
+                bufsk = new double[0,0];
+                bufrho = new double[0];
+                buftheta = new double[0];
+                smonitor = new optserv.smoothnessmonitor();
+                lastscaleused = new double[0];
+                invs = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                minbleicstate _result = new minbleicstate();
+                _result.nmain = nmain;
+                _result.nslack = nslack;
+                _result.epsg = epsg;
+                _result.epsf = epsf;
+                _result.epsx = epsx;
+                _result.maxits = maxits;
+                _result.xrep = xrep;
+                _result.drep = drep;
+                _result.stpmax = stpmax;
+                _result.diffstep = diffstep;
+                _result.sas = (sactivesets.sactiveset)sas.make_copy();
+                _result.s = (double[])s.Clone();
+                _result.prectype = prectype;
+                _result.diagh = (double[])diagh.Clone();
+                _result.x = (double[])x.Clone();
+                _result.f = f;
+                _result.g = (double[])g.Clone();
+                _result.needf = needf;
+                _result.needfg = needfg;
+                _result.xupdated = xupdated;
+                _result.lsstart = lsstart;
+                _result.steepestdescentstep = steepestdescentstep;
+                _result.boundedstep = boundedstep;
+                _result.userterminationneeded = userterminationneeded;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.ugc = (double[])ugc.Clone();
+                _result.cgc = (double[])cgc.Clone();
+                _result.xn = (double[])xn.Clone();
+                _result.ugn = (double[])ugn.Clone();
+                _result.cgn = (double[])cgn.Clone();
+                _result.xp = (double[])xp.Clone();
+                _result.fc = fc;
+                _result.fn = fn;
+                _result.fp = fp;
+                _result.d = (double[])d.Clone();
+                _result.cleic = (double[,])cleic.Clone();
+                _result.nec = nec;
+                _result.nic = nic;
+                _result.lastgoodstep = lastgoodstep;
+                _result.lastscaledgoodstep = lastscaledgoodstep;
+                _result.maxscaledgrad = maxscaledgrad;
+                _result.hasbndl = (bool[])hasbndl.Clone();
+                _result.hasbndu = (bool[])hasbndu.Clone();
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.repinneriterationscount = repinneriterationscount;
+                _result.repouteriterationscount = repouteriterationscount;
+                _result.repnfev = repnfev;
+                _result.repvaridx = repvaridx;
+                _result.repterminationtype = repterminationtype;
+                _result.repdebugeqerr = repdebugeqerr;
+                _result.repdebugfs = repdebugfs;
+                _result.repdebugff = repdebugff;
+                _result.repdebugdx = repdebugdx;
+                _result.repdebugfeasqpits = repdebugfeasqpits;
+                _result.repdebugfeasgpaits = repdebugfeasgpaits;
+                _result.xstart = (double[])xstart.Clone();
+                _result.solver = (snnls.snnlssolver)solver.make_copy();
+                _result.fbase = fbase;
+                _result.fm2 = fm2;
+                _result.fm1 = fm1;
+                _result.fp1 = fp1;
+                _result.fp2 = fp2;
+                _result.xm1 = xm1;
+                _result.xp1 = xp1;
+                _result.gm1 = gm1;
+                _result.gp1 = gp1;
+                _result.cidx = cidx;
+                _result.cval = cval;
+                _result.tmpprec = (double[])tmpprec.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.nfev = nfev;
+                _result.mcstage = mcstage;
+                _result.stp = stp;
+                _result.curstpmax = curstpmax;
+                _result.activationstep = activationstep;
+                _result.work = (double[])work.Clone();
+                _result.lstate = (linmin.linminstate)lstate.make_copy();
+                _result.trimthreshold = trimthreshold;
+                _result.nonmonotoniccnt = nonmonotoniccnt;
+                _result.bufyk = (double[,])bufyk.Clone();
+                _result.bufsk = (double[,])bufsk.Clone();
+                _result.bufrho = (double[])bufrho.Clone();
+                _result.buftheta = (double[])buftheta.Clone();
+                _result.bufsize = bufsize;
+                _result.teststep = teststep;
+                _result.smoothnessguardlevel = smoothnessguardlevel;
+                _result.smonitor = (optserv.smoothnessmonitor)smonitor.make_copy();
+                _result.lastscaleused = (double[])lastscaleused.Clone();
+                _result.invs = (double[])invs.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure stores optimization report:
+        * IterationsCount           number of iterations
+        * NFEV                      number of gradient evaluations
+        * TerminationType           termination type (see below)
+
+        TERMINATION CODES
+
+        TerminationType field contains completion code, which can be:
+          -8    internal integrity control detected  infinite  or  NAN  values  in
+                function/gradient. Abnormal termination signalled.
+          -3    inconsistent constraints. Feasible point is
+                either nonexistent or too hard to find. Try to
+                restart optimizer with better initial approximation
+           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,
+                X contains best point found so far.
+           8    terminated by user who called minbleicrequesttermination(). X contains
+                point which was "current accepted" when  termination  request  was
+                submitted.
+
+        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 : apobject
+        {
+            public int iterationscount;
+            public int nfev;
+            public int varidx;
+            public int terminationtype;
+            public double debugeqerr;
+            public double debugfs;
+            public double debugff;
+            public double debugdx;
+            public int debugfeasqpits;
+            public int debugfeasgpaits;
+            public int inneriterationscount;
+            public int outeriterationscount;
+            public minbleicreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                minbleicreport _result = new minbleicreport();
+                _result.iterationscount = iterationscount;
+                _result.nfev = nfev;
+                _result.varidx = varidx;
+                _result.terminationtype = terminationtype;
+                _result.debugeqerr = debugeqerr;
+                _result.debugfs = debugfs;
+                _result.debugff = debugff;
+                _result.debugdx = debugdx;
+                _result.debugfeasqpits = debugfeasqpits;
+                _result.debugfeasgpaits = debugfeasgpaits;
+                _result.inneriterationscount = inneriterationscount;
+                _result.outeriterationscount = outeriterationscount;
+                return _result;
+            }
+        };
+
+
+
+
+        public const double gtol = 0.4;
+        public const double maxnonmonotoniclen = 1.0E-7;
+        public const double nmstol = 1.0E2;
+        public const double initialdecay = 0.5;
+        public const double mindecay = 0.1;
+        public const double decaycorrection = 0.8;
+        public const double penaltyfactor = 100;
+
+
+        /*************************************************************************
+                             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 with MinBLEICSetCond().
+
+        4. User calls MinBLEICOptimize() function which takes algorithm  state and
+           pointer (delegate, etc.) to callback function which calculates F/G.
+
+        5. User calls MinBLEICResults() to get solution
+
+        6. Optionally user may call MinBLEICRestartFrom() to solve another problem
+           with same N but another starting point.
+           MinBLEICRestartFrom() allows to reuse already initialized structure.
+
+        NOTE: if you have box-only constraints (no  general  linear  constraints),
+              then MinBC optimizer can be better option. It uses  special,  faster
+              constraint activation method, which performs better on problems with
+              multiple constraints active at the solution.
+              
+              On small-scale problems performance of MinBC is similar to  that  of
+              MinBLEIC, but on large-scale ones (hundreds and thousands of  active
+              constraints) it can be several times faster than MinBLEIC.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "MinBLEICCreate: X contains infinite or NaN values!");
+            minbleicinitinternal(n, x, 0.0, state, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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().
+
+        NOTE: if you have box-only constraints (no  general  linear  constraints),
+              then MinBC optimizer can be better option. It uses  special,  faster
+              constraint activation method, which performs better on problems with
+              multiple constraints active at the solution.
+              
+              On small-scale problems performance of MinBC is similar to  that  of
+              MinBLEIC, but on large-scale ones (hundreds and thousands of  active
+              constraints) it can be several times faster than MinBLEIC.
+              
+        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,
+            alglib.xparams _params)
+        {
+            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.bndl[i] = bndl[i];
+                state.hasbndl[i] = math.isfinite(bndl[i]);
+                state.bndu[i] = bndu[i];
+                state.hasbndu[i] = math.isfinite(bndu[i]);
+            }
+            sactivesets.sassetbc(state.sas, bndl, bndu, _params);
+        }
+
+
+        /*************************************************************************
+        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]
+                        * if CT[i]=0, then I-th constraint is C[i,*]*x  = C[i,n]
+                        * if CT[i]<0, then I-th constraint is C[i,*]*x <= C[i,n]
+            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,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int i_ = 0;
+
+            n = state.nmain;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(k>=0, "MinBLEICSetLC: K<0");
+            alglib.ap.assert(alglib.ap.cols(c)>=n+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, n+1, _params), "MinBLEICSetLC: C contains infinite or NaN values!");
+            
+            //
+            // Handle zero K
+            //
+            if( k==0 )
+            {
+                state.nec = 0;
+                state.nic = 0;
+                sactivesets.sassetlc(state.sas, c, ct, 0, _params);
+                return;
+            }
+            
+            //
+            // Equality constraints are stored first, in the upper
+            // NEC rows of State.CLEIC matrix. Inequality constraints
+            // are stored in the next NIC rows.
+            //
+            // NOTE: we convert inequality constraints to the form
+            // A*x<=b before copying them.
+            //
+            apserv.rmatrixsetlengthatleast(ref state.cleic, k, n+1, _params);
+            state.nec = 0;
+            state.nic = 0;
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]==0 )
+                {
+                    for(i_=0; i_<=n;i_++)
+                    {
+                        state.cleic[state.nec,i_] = c[i,i_];
+                    }
+                    state.nec = state.nec+1;
+                }
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]!=0 )
+                {
+                    if( ct[i]>0 )
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec+state.nic,i_] = -c[i,i_];
+                        }
+                    }
+                    else
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec+state.nic,i_] = c[i,i_];
+                        }
+                    }
+                    state.nic = state.nic+1;
+                }
+            }
+            
+            //
+            // Normalize rows of State.CLEIC: each row must have unit norm.
+            // Norm is calculated using first N elements (i.e. right part is
+            // not counted when we calculate norm).
+            //
+            for(i=0; i<=k-1; i++)
+            {
+                v = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+math.sqr(state.cleic[i,j]);
+                }
+                if( (double)(v)==(double)(0) )
+                {
+                    continue;
+                }
+                v = 1/Math.Sqrt(v);
+                for(i_=0; i_<=n;i_++)
+                {
+                    state.cleic[i,i_] = v*state.cleic[i,i_];
+                }
+            }
+            sactivesets.sassetlc(state.sas, c, ct, k, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets stopping conditions for the optimizer.
+
+        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 - step vector, dx=X(k+1)-X(k)
+                        * s - scaling coefficients set by MinBLEICSetScale()
+            MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                        iterations is unlimited.
+
+        Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead
+        to automatic stopping criterion selection.
+
+        NOTE: when SetCond() called with non-zero MaxIts, BLEIC solver may perform
+              slightly more than MaxIts iterations. I.e., MaxIts  sets  non-strict
+              limit on iterations count.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicsetcond(minbleicstate state,
+            double epsg,
+            double epsf,
+            double epsx,
+            int maxits,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(epsg), "MinBLEICSetCond: EpsG is not finite number");
+            alglib.ap.assert((double)(epsg)>=(double)(0), "MinBLEICSetCond: negative EpsG");
+            alglib.ap.assert(math.isfinite(epsf), "MinBLEICSetCond: EpsF is not finite number");
+            alglib.ap.assert((double)(epsf)>=(double)(0), "MinBLEICSetCond: negative EpsF");
+            alglib.ap.assert(math.isfinite(epsx), "MinBLEICSetCond: EpsX is not finite number");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "MinBLEICSetCond: negative EpsX");
+            alglib.ap.assert(maxits>=0, "MinBLEICSetCond: 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 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,
+            alglib.xparams _params)
+        {
+            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.s[i] = Math.Abs(s[i]);
+            }
+            sactivesets.sassetscale(state.sas, s, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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.diagh, state.nmain, _params);
+            state.prectype = 2;
+            for(i=0; i<=state.nmain-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  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,
+            alglib.xparams _params)
+        {
+            state.prectype = 3;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        This function turns on/off line search reports.
+        These reports are described in more details in developer-only  comments on
+        MinBLEICState 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 minbleicsetdrep(minbleicstate state,
+            bool needdrep,
+            alglib.xparams _params)
+        {
+            state.drep = needdrep;
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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 MinBLEICOptimize()) 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,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int m = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double vv = 0;
+            double v0 = 0;
+            bool b = new bool();
+            int mcinfo = 0;
+            int actstatus = 0;
+            int itidx = 0;
+            double penalty = 0;
+            double ginit = 0;
+            double gdecay = 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];
+                mcinfo = state.rstate.ia[4];
+                actstatus = state.rstate.ia[5];
+                itidx = state.rstate.ia[6];
+                b = state.rstate.ba[0];
+                v = state.rstate.ra[0];
+                vv = state.rstate.ra[1];
+                v0 = state.rstate.ra[2];
+                penalty = state.rstate.ra[3];
+                ginit = state.rstate.ra[4];
+                gdecay = state.rstate.ra[5];
+            }
+            else
+            {
+                n = 359;
+                m = -58;
+                i = -919;
+                j = -909;
+                mcinfo = 81;
+                actstatus = 255;
+                itidx = 74;
+                b = false;
+                v = 809;
+                vv = 205;
+                v0 = -838;
+                penalty = 939;
+                ginit = -526;
+                gdecay = 763;
+            }
+            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;
+            }
+            if( state.rstate.stage==17 )
+            {
+                goto lbl_17;
+            }
+            if( state.rstate.stage==18 )
+            {
+                goto lbl_18;
+            }
+            if( state.rstate.stage==19 )
+            {
+                goto lbl_19;
+            }
+            if( state.rstate.stage==20 )
+            {
+                goto lbl_20;
+            }
+            if( state.rstate.stage==21 )
+            {
+                goto lbl_21;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // Algorithm parameters:
+            // * M          number of L-BFGS corrections.
+            //              This coefficient remains fixed during iterations.
+            // * GDecay     desired decrease of constrained gradient during L-BFGS iterations.
+            //              This coefficient is decreased after each L-BFGS round until
+            //              it reaches minimum decay.
+            //
+            m = Math.Min(5, state.nmain);
+            gdecay = initialdecay;
+            
+            //
+            // Init
+            //
+            n = state.nmain;
+            state.steepestdescentstep = false;
+            state.userterminationneeded = false;
+            state.repterminationtype = 0;
+            state.repinneriterationscount = 0;
+            state.repouteriterationscount = 0;
+            state.repnfev = 0;
+            state.repvaridx = -1;
+            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;
+            }
+            apserv.rmatrixsetlengthatleast(ref state.bufyk, m+1, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.bufsk, m+1, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.bufrho, m, _params);
+            apserv.rvectorsetlengthatleast(ref state.buftheta, m, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmp0, n, _params);
+            optserv.smoothnessmonitorinit(state.smonitor, n, 1, state.smoothnessguardlevel>0, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.lastscaleused[i] = state.s[i];
+                state.invs[i] = 1/state.s[i];
+            }
+            
+            //
+            //  Check analytic derivative
+            //
+            clearrequestfields(state, _params);
+            if( !((double)(state.diffstep)==(double)(0) && (double)(state.teststep)>(double)(0)) )
+            {
+                goto lbl_22;
+            }
+        lbl_24:
+            if( !optserv.smoothnessmonitorcheckgradientatx0(state.smonitor, state.xstart, state.s, state.bndl, state.bndu, true, state.teststep, _params) )
+            {
+                goto lbl_25;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.smonitor.x[i];
+            }
+            state.needfg = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfg = false;
+            state.smonitor.fi[0] = state.f;
+            for(i=0; i<=n-1; i++)
+            {
+                state.smonitor.j[0,i] = state.g[i];
+            }
+            goto lbl_24;
+        lbl_25:
+        lbl_22:
+            
+            //
+            // Fill TmpPrec with current preconditioner
+            //
+            apserv.rvectorsetlengthatleast(ref state.tmpprec, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.prectype==2 )
+                {
+                    state.tmpprec[i] = state.diagh[i];
+                    continue;
+                }
+                if( state.prectype==3 )
+                {
+                    state.tmpprec[i] = 1/math.sqr(state.s[i]);
+                    continue;
+                }
+                state.tmpprec[i] = 1;
+            }
+            sactivesets.sassetprecdiag(state.sas, state.tmpprec, _params);
+            
+            //
+            // Start optimization
+            //
+            if( !sactivesets.sasstartoptimization(state.sas, state.xstart, _params) )
+            {
+                state.repterminationtype = -3;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Main cycle of BLEIC-PG algorithm
+            //
+            state.repterminationtype = 0;
+            state.lastgoodstep = 0;
+            state.lastscaledgoodstep = 0;
+            state.maxscaledgrad = 0;
+            state.nonmonotoniccnt = (int)Math.Round(1.5*(n+state.nic))+5;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.sas.xc[i_];
+            }
+            clearrequestfields(state, _params);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_26;
+            }
+            state.needfg = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.needfg = false;
+            goto lbl_27;
+        lbl_26:
+            state.needf = true;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.needf = false;
+        lbl_27:
+            state.fc = state.f;
+            optserv.trimprepare(state.f, ref state.trimthreshold, _params);
+            state.repnfev = state.repnfev+1;
+            if( !state.xrep )
+            {
+                goto lbl_28;
+            }
+            
+            //
+            // Report current point
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.sas.xc[i_];
+            }
+            state.f = state.fc;
+            state.xupdated = true;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.xupdated = false;
+        lbl_28:
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                sactivesets.sasstopoptimization(state.sas, _params);
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+        lbl_30:
+            if( false )
+            {
+                goto lbl_31;
+            }
+            
+            //
+            // Preparations
+            //
+            // (a) calculate unconstrained gradient
+            // (b) determine initial active set
+            // (c) update MaxScaledGrad
+            // (d) check F/G for NAN/INF, abnormally terminate algorithm if needed
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.sas.xc[i_];
+            }
+            clearrequestfields(state, _params);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_32;
+            }
+            
+            //
+            // Analytic gradient
+            //
+            state.needfg = true;
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.needfg = false;
+            goto lbl_33;
+        lbl_32:
+            
+            //
+            // Numerical differentiation
+            //
+            state.needf = true;
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.fbase = state.f;
+            i = 0;
+        lbl_34:
+            if( i>n-1 )
+            {
+                goto lbl_36;
+            }
+            v = state.x[i];
+            b = false;
+            if( state.hasbndl[i] )
+            {
+                b = b || (double)(v-state.diffstep*state.s[i])<(double)(state.bndl[i]);
+            }
+            if( state.hasbndu[i] )
+            {
+                b = b || (double)(v+state.diffstep*state.s[i])>(double)(state.bndu[i]);
+            }
+            if( b )
+            {
+                goto lbl_37;
+            }
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 8;
+            goto lbl_rcomm;
+        lbl_8:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 9;
+            goto lbl_rcomm;
+        lbl_9:
+            state.fp2 = state.f;
+            state.g[i] = (8*(state.fp1-state.fm1)-(state.fp2-state.fm2))/(6*state.diffstep*state.s[i]);
+            goto lbl_38;
+        lbl_37:
+            state.xm1 = v-state.diffstep*state.s[i];
+            state.xp1 = v+state.diffstep*state.s[i];
+            if( state.hasbndl[i] && (double)(state.xm1)<(double)(state.bndl[i]) )
+            {
+                state.xm1 = state.bndl[i];
+            }
+            if( state.hasbndu[i] && (double)(state.xp1)>(double)(state.bndu[i]) )
+            {
+                state.xp1 = state.bndu[i];
+            }
+            state.x[i] = state.xm1;
+            state.rstate.stage = 10;
+            goto lbl_rcomm;
+        lbl_10:
+            state.fm1 = state.f;
+            state.x[i] = state.xp1;
+            state.rstate.stage = 11;
+            goto lbl_rcomm;
+        lbl_11:
+            state.fp1 = state.f;
+            if( (double)(state.xm1)!=(double)(state.xp1) )
+            {
+                state.g[i] = (state.fp1-state.fm1)/(state.xp1-state.xm1);
+            }
+            else
+            {
+                state.g[i] = 0;
+            }
+        lbl_38:
+            state.x[i] = v;
+            i = i+1;
+            goto lbl_34;
+        lbl_36:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_33:
+            state.fc = state.f;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ugc[i_] = state.g[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.cgc[i_] = state.g[i_];
+            }
+            sactivesets.sasreactivateconstraintsprec(state.sas, state.ugc, _params);
+            sactivesets.sasconstraineddirection(state.sas, ref state.cgc, _params);
+            ginit = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                ginit = ginit+math.sqr(state.cgc[i]*state.s[i]);
+            }
+            ginit = Math.Sqrt(ginit);
+            state.maxscaledgrad = Math.Max(state.maxscaledgrad, ginit);
+            if( !math.isfinite(ginit) || !math.isfinite(state.fc) )
+            {
+                
+                //
+                // Abnormal termination - infinities in function/gradient
+                //
+                sactivesets.sasstopoptimization(state.sas, _params);
+                state.repterminationtype = -8;
+                result = false;
+                return result;
+            }
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                sactivesets.sasstopoptimization(state.sas, _params);
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            
+            //
+            // LBFGS stage:
+            // * during LBFGS iterations we activate new constraints, but never
+            //   deactivate already active ones.
+            // * we perform at most N iterations of LBFGS before re-evaluating
+            //   active set and restarting LBFGS.
+            // * first iteration of LBFGS is a special - it is performed with
+            //   minimum set of active constraints, algorithm termination can
+            //   be performed only at this state. We call this iteration
+            //  "steepest descent step".
+            //
+            // About termination:
+            // * LBFGS iterations can be terminated because of two reasons:
+            //   * "termination" - non-zero termination code in RepTerminationType,
+            //     which means that optimization is done
+            //   * "restart" - zero RepTerminationType, which means that we
+            //     have to re-evaluate active set and resume LBFGS stage.
+            // * one more option is "refresh" - to continue LBFGS iterations,
+            //   but with all BFGS updates (Sk/Yk pairs) being dropped;
+            //   it happens after changes in active set
+            //
+            state.bufsize = 0;
+            state.steepestdescentstep = true;
+            itidx = -1;
+        lbl_39:
+            if( itidx>=n-1 )
+            {
+                goto lbl_40;
+            }
+            
+            //
+            // Increment iterations counter
+            //
+            // NOTE: we have strong reasons to use such complex scheme
+            //       instead of just for() loop - this counter may be
+            //       decreased at some occasions to perform "restart"
+            //       of an iteration.
+            //
+            itidx = itidx+1;
+            
+            //
+            // At the beginning of each iteration:
+            // * SAS.XC stores current point
+            // * FC stores current function value
+            // * UGC stores current unconstrained gradient
+            // * CGC stores current constrained gradient
+            // * D stores constrained step direction (calculated at this block)
+            //
+            //
+            // Check gradient-based stopping criteria
+            //
+            // This stopping condition is tested only for step which is the
+            // first step of LBFGS (subsequent steps may accumulate active
+            // constraints thus they should NOT be used for stopping - gradient
+            // may be small when constrained, but these constraints may be
+            // deactivated by the subsequent steps)
+            //
+            if( state.steepestdescentstep && (double)(sactivesets.sasscaledconstrainednorm(state.sas, state.ugc, _params))<=(double)(state.epsg) )
+            {
+                
+                //
+                // Gradient is small enough.
+                // Optimization is terminated
+                //
+                state.repterminationtype = 4;
+                goto lbl_40;
+            }
+            
+            //
+            // 1. Calculate search direction D according to L-BFGS algorithm
+            //    using constrained preconditioner to perform inner multiplication.
+            // 2. Evaluate scaled length of direction D; restart LBFGS if D is zero
+            //    (it may be possible that we found minimum, but it is also possible
+            //    that some constraints need deactivation)
+            // 3. If D is non-zero, try to use previous scaled step length as initial estimate for new step.
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.work[i_] = state.cgc[i_];
+            }
+            for(i=state.bufsize-1; i>=0; i--)
+            {
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.bufsk[i,i_]*state.work[i_];
+                }
+                state.buftheta[i] = v;
+                vv = v*state.bufrho[i];
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.work[i_] = state.work[i_] - vv*state.bufyk[i,i_];
+                }
+            }
+            sactivesets.sasconstraineddirectionprec(state.sas, ref state.work, _params);
+            for(i=0; i<=state.bufsize-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.bufyk[i,i_]*state.work[i_];
+                }
+                vv = state.bufrho[i]*(-v+state.buftheta[i]);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.work[i_] = state.work[i_] + vv*state.bufsk[i,i_];
+                }
+            }
+            sactivesets.sasconstraineddirection(state.sas, ref state.work, _params);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.d[i_] = -state.work[i_];
+            }
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.d[i]/state.s[i]);
+            }
+            v = Math.Sqrt(v);
+            if( (double)(v)==(double)(0) )
+            {
+                
+                //
+                // Search direction is zero.
+                // If we perform "steepest descent step", algorithm is terminated.
+                // Otherwise we just restart LBFGS.
+                //
+                if( state.steepestdescentstep )
+                {
+                    state.repterminationtype = 4;
+                }
+                goto lbl_40;
+            }
+            alglib.ap.assert((double)(v)>(double)(0), "MinBLEIC: internal error");
+            if( (double)(state.lastscaledgoodstep)>(double)(0) && (double)(v)>(double)(0) )
+            {
+                state.stp = state.lastscaledgoodstep/v;
+            }
+            else
+            {
+                state.stp = 1.0/v;
+            }
+            
+            //
+            // Calculate bound on step length.
+            // Step direction is stored
+            //
+            sactivesets.sasexploredirection(state.sas, state.d, ref state.curstpmax, ref state.cidx, ref state.cval, _params);
+            state.activationstep = state.curstpmax;
+            if( state.cidx>=0 && (double)(state.activationstep)==(double)(0) )
+            {
+                
+                //
+                // We are exactly at the boundary, immediate activation
+                // of constraint is required. LBFGS stage is continued
+                // with "refreshed" model.
+                //
+                // ! IMPORTANT: we do not clear SteepestDescent flag here,
+                // !            it is very important for correct stopping
+                // !            of algorithm.
+                //
+                // ! IMPORTANT: we decrease iteration counter in order to
+                //              preserve computational budget for iterations.
+                //
+                sactivesets.sasimmediateactivation(state.sas, state.cidx, state.cval, _params);
+                state.bufsize = 0;
+                itidx = itidx-1;
+                goto lbl_39;
+            }
+            if( (double)(state.stpmax)>(double)(0) )
+            {
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.d[i_]*state.d[i_];
+                }
+                v = Math.Sqrt(v);
+                if( (double)(v)>(double)(0) )
+                {
+                    state.curstpmax = Math.Min(state.curstpmax, state.stpmax/v);
+                }
+            }
+            
+            //
+            // Report beginning of line search (if requested by caller).
+            // See description of the MinBLEICState for more information
+            // about fields accessible to caller.
+            //
+            // Caller may do following:
+            // * change State.Stp and load better initial estimate of
+            //   the step length.
+            // Caller may not terminate algorithm.
+            //
+            if( !state.drep )
+            {
+                goto lbl_41;
+            }
+            clearrequestfields(state, _params);
+            state.lsstart = true;
+            state.boundedstep = state.cidx>=0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.sas.xc[i_];
+            }
+            state.rstate.stage = 12;
+            goto lbl_rcomm;
+        lbl_12:
+            state.lsstart = false;
+        lbl_41:
+            
+            //
+            // Minimize F(x+alpha*d)
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xn[i_] = state.sas.xc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.cgn[i_] = state.cgc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ugn[i_] = state.ugc[i_];
+            }
+            state.fn = state.fc;
+            state.mcstage = 0;
+            optserv.smoothnessmonitorstartlinesearch1u(state.smonitor, state.s, state.invs, state.xn, state.fn, state.ugn, _params);
+            linmin.mcsrch(n, ref state.xn, ref state.fn, ref state.ugn, state.d, ref state.stp, state.curstpmax, gtol, ref mcinfo, ref state.nfev, ref state.work, state.lstate, ref state.mcstage, _params);
+        lbl_43:
+            if( state.mcstage==0 )
+            {
+                goto lbl_44;
+            }
+            
+            //
+            // Perform correction (constraints are enforced)
+            // Copy XN to X
+            //
+            sactivesets.sascorrection(state.sas, state.xn, ref penalty, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.xn[i];
+            }
+            
+            //
+            // Gradient, either user-provided or numerical differentiation
+            //
+            clearrequestfields(state, _params);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_45;
+            }
+            
+            //
+            // Analytic gradient
+            //
+            state.needfg = true;
+            state.rstate.stage = 13;
+            goto lbl_rcomm;
+        lbl_13:
+            state.needfg = false;
+            state.repnfev = state.repnfev+1;
+            goto lbl_46;
+        lbl_45:
+            
+            //
+            // Numerical differentiation
+            //
+            state.needf = true;
+            state.rstate.stage = 14;
+            goto lbl_rcomm;
+        lbl_14:
+            state.fbase = state.f;
+            i = 0;
+        lbl_47:
+            if( i>n-1 )
+            {
+                goto lbl_49;
+            }
+            v = state.x[i];
+            b = false;
+            if( state.hasbndl[i] )
+            {
+                b = b || (double)(v-state.diffstep*state.s[i])<(double)(state.bndl[i]);
+            }
+            if( state.hasbndu[i] )
+            {
+                b = b || (double)(v+state.diffstep*state.s[i])>(double)(state.bndu[i]);
+            }
+            if( b )
+            {
+                goto lbl_50;
+            }
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 15;
+            goto lbl_rcomm;
+        lbl_15:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 16;
+            goto lbl_rcomm;
+        lbl_16:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 17;
+            goto lbl_rcomm;
+        lbl_17:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 18;
+            goto lbl_rcomm;
+        lbl_18:
+            state.fp2 = state.f;
+            state.g[i] = (8*(state.fp1-state.fm1)-(state.fp2-state.fm2))/(6*state.diffstep*state.s[i]);
+            state.repnfev = state.repnfev+4;
+            goto lbl_51;
+        lbl_50:
+            state.xm1 = v-state.diffstep*state.s[i];
+            state.xp1 = v+state.diffstep*state.s[i];
+            if( state.hasbndl[i] && (double)(state.xm1)<(double)(state.bndl[i]) )
+            {
+                state.xm1 = state.bndl[i];
+            }
+            if( state.hasbndu[i] && (double)(state.xp1)>(double)(state.bndu[i]) )
+            {
+                state.xp1 = state.bndu[i];
+            }
+            state.x[i] = state.xm1;
+            state.rstate.stage = 19;
+            goto lbl_rcomm;
+        lbl_19:
+            state.fm1 = state.f;
+            state.x[i] = state.xp1;
+            state.rstate.stage = 20;
+            goto lbl_rcomm;
+        lbl_20:
+            state.fp1 = state.f;
+            if( (double)(state.xm1)!=(double)(state.xp1) )
+            {
+                state.g[i] = (state.fp1-state.fm1)/(state.xp1-state.xm1);
+            }
+            else
+            {
+                state.g[i] = 0;
+            }
+            state.repnfev = state.repnfev+2;
+        lbl_51:
+            state.x[i] = v;
+            i = i+1;
+            goto lbl_47;
+        lbl_49:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_46:
+            
+            //
+            // Back to MCSRCH
+            //
+            // NOTE: penalty term from correction is added to FN in order
+            //       to penalize increase in infeasibility.
+            //
+            optserv.smoothnessmonitorenqueuepoint1u(state.smonitor, state.s, state.invs, state.d, state.stp, state.x, state.f, state.g, _params);
+            state.fn = state.f+penaltyfactor*state.maxscaledgrad*penalty;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.cgn[i_] = state.g[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ugn[i_] = state.g[i_];
+            }
+            sactivesets.sasconstraineddirection(state.sas, ref state.cgn, _params);
+            optserv.trimfunction(ref state.fn, ref state.cgn, n, state.trimthreshold, _params);
+            linmin.mcsrch(n, ref state.xn, ref state.fn, ref state.ugn, state.d, ref state.stp, state.curstpmax, gtol, ref mcinfo, ref state.nfev, ref state.work, state.lstate, ref state.mcstage, _params);
+            goto lbl_43;
+        lbl_44:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.bufsk[state.bufsize,i_] = -state.sas.xc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.bufyk[state.bufsize,i_] = -state.cgc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.bufsk[state.bufsize,i_] = state.bufsk[state.bufsize,i_] + state.xn[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.bufyk[state.bufsize,i_] = state.bufyk[state.bufsize,i_] + state.cgn[i_];
+            }
+            optserv.smoothnessmonitorfinalizelinesearch(state.smonitor, _params);
+            
+            //
+            // Check for presence of NAN/INF in function/gradient
+            //
+            v = state.fn;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.1*v+state.ugn[i];
+            }
+            if( !math.isfinite(v) )
+            {
+                
+                //
+                // Abnormal termination - infinities in function/gradient
+                //
+                state.repterminationtype = -8;
+                goto lbl_40;
+            }
+            
+            //
+            // Handle possible failure of the line search or request for termination
+            //
+            if( mcinfo!=1 && mcinfo!=5 )
+            {
+                
+                //
+                // We can not find step which decreases function value. We have
+                // two possibilities:
+                // (a) numerical properties of the function do not allow us to
+                //     find good step.
+                // (b) we are close to activation of some constraint, and it is
+                //     so close that step which activates it leads to change in
+                //     target function which is smaller than numerical noise.
+                //
+                // Optimization algorithm must be able to handle case (b), because
+                // inability to handle it will cause failure when algorithm
+                // started very close to boundary of the feasible area.
+                //
+                // In order to correctly handle such cases we allow limited amount
+                // of small steps which increase function value.
+                //
+                v = 0.0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v = v+math.sqr(state.d[i]*state.curstpmax/state.s[i]);
+                }
+                v = Math.Sqrt(v);
+                b = false;
+                if( (state.cidx>=0 && (double)(v)<=(double)(maxnonmonotoniclen)) && state.nonmonotoniccnt>0 )
+                {
+                    
+                    //
+                    // We try to enforce non-monotonic step:
+                    // * Stp    := CurStpMax
+                    // * MCINFO := 5
+                    // * XN     := XC+CurStpMax*D
+                    // * non-monotonic counter is decreased
+                    //
+                    // NOTE: UGN/CGN are not updated because step is so short that we assume that
+                    //       GN is approximately equal to GC.
+                    //
+                    // NOTE: prior to enforcing such step we check that it does not increase infeasibility
+                    //       of constraints beyond tolerable level
+                    //
+                    v = state.curstpmax;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.tmp0[i_] = state.sas.xc[i_];
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.tmp0[i_] = state.tmp0[i_] + v*state.d[i_];
+                    }
+                    if( (double)(feasibilityerror(state.tmp0, state.s, n, state.cleic, state.nec, state.nic, _params))<=(double)(nmstol*Math.Sqrt(n)*math.machineepsilon) )
+                    {
+                        state.stp = state.curstpmax;
+                        mcinfo = 5;
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            state.xn[i_] = state.tmp0[i_];
+                        }
+                        state.nonmonotoniccnt = state.nonmonotoniccnt-1;
+                        b = true;
+                    }
+                }
+                if( !b )
+                {
+                    
+                    //
+                    // Numerical properties of the function do not allow
+                    // us to solve problem. Here we have two possibilities:
+                    // * if it is "steepest descent" step, we can terminate
+                    //   algorithm because we are close to minimum
+                    // * if it is NOT "steepest descent" step, we should restart
+                    //   LBFGS iterations.
+                    //
+                    if( state.steepestdescentstep )
+                    {
+                        
+                        //
+                        // Algorithm is terminated
+                        //
+                        state.repterminationtype = 7;
+                        goto lbl_40;
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Re-evaluate active set and restart LBFGS
+                        //
+                        goto lbl_40;
+                    }
+                }
+            }
+            if( state.userterminationneeded )
+            {
+                goto lbl_40;
+            }
+            
+            //
+            // Current point is updated:
+            // * move XC/FC/GC to XP/FP/GP
+            // * change current point remembered by SAS structure
+            // * move XN/FN/GN to XC/FC/GC
+            // * report current point and update iterations counter
+            // * if MCINFO=1, push new pair SK/YK to LBFGS buffer
+            //
+            state.fp = state.fc;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xp[i_] = state.sas.xc[i_];
+            }
+            state.fc = state.fn;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.cgc[i_] = state.cgn[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ugc[i_] = state.ugn[i_];
+            }
+            actstatus = sactivesets.sasmoveto(state.sas, state.xn, state.cidx>=0 && (double)(state.stp)>=(double)(state.activationstep), state.cidx, state.cval, _params);
+            if( !state.xrep )
+            {
+                goto lbl_52;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.sas.xc[i_];
+            }
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 21;
+            goto lbl_rcomm;
+        lbl_21:
+            state.xupdated = false;
+        lbl_52:
+            state.repinneriterationscount = state.repinneriterationscount+1;
+            if( mcinfo==1 )
+            {
+                
+                //
+                // Accept new LBFGS update given by Sk,Yk
+                //
+                if( state.bufsize==m )
+                {
+                    
+                    //
+                    // Buffer is full, shift contents by one row
+                    //
+                    for(i=0; i<=state.bufsize-1; i++)
+                    {
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            state.bufsk[i,i_] = state.bufsk[i+1,i_];
+                        }
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            state.bufyk[i,i_] = state.bufyk[i+1,i_];
+                        }
+                    }
+                    for(i=0; i<=state.bufsize-2; i++)
+                    {
+                        state.bufrho[i] = state.bufrho[i+1];
+                        state.buftheta[i] = state.buftheta[i+1];
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Buffer is not full, increase buffer size by 1
+                    //
+                    state.bufsize = state.bufsize+1;
+                }
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.bufyk[state.bufsize-1,i_]*state.bufsk[state.bufsize-1,i_];
+                }
+                vv = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    vv += state.bufyk[state.bufsize-1,i_]*state.bufyk[state.bufsize-1,i_];
+                }
+                if( (double)(v)==(double)(0) || (double)(vv)==(double)(0) )
+                {
+                    
+                    //
+                    // Strange internal error in LBFGS - either YK=0
+                    // (which should not have been) or (SK,YK)=0 (again,
+                    // unexpected). It should not take place because
+                    // MCINFO=1, which signals "good" step. But just
+                    // to be sure we have special branch of code which
+                    // restarts LBFGS
+                    //
+                    goto lbl_40;
+                }
+                state.bufrho[state.bufsize-1] = 1/v;
+                alglib.ap.assert(state.bufsize<=m, "MinBLEIC: internal error");
+                
+                //
+                // Update length of the good step
+                //
+                v = 0;
+                vv = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v = v+math.sqr((state.sas.xc[i]-state.xp[i])/state.s[i]);
+                    vv = vv+math.sqr(state.sas.xc[i]-state.xp[i]);
+                }
+                state.lastgoodstep = Math.Sqrt(vv);
+                updateestimateofgoodstep(ref state.lastscaledgoodstep, Math.Sqrt(v), _params);
+            }
+            
+            //
+            // Check stopping criteria
+            //
+            // Step size and function-based stopping criteria are tested only
+            // for step which satisfies Wolfe conditions and is the first step of
+            // LBFGS (subsequent steps may accumulate active constraints thus
+            // they should NOT be used for stopping; step size or function change
+            // may be small when constrained, but these constraints may be
+            // deactivated by the subsequent steps).
+            //
+            // MaxIts-based stopping condition is checked for all kinds of steps.
+            //
+            if( mcinfo==1 && state.steepestdescentstep )
+            {
+                
+                //
+                // Step is small enough
+                //
+                v = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v = v+math.sqr((state.sas.xc[i]-state.xp[i])/state.s[i]);
+                }
+                v = Math.Sqrt(v);
+                if( (double)(v)<=(double)(state.epsx) )
+                {
+                    state.repterminationtype = 2;
+                    goto lbl_40;
+                }
+                
+                //
+                // Function change is small enough
+                //
+                if( (double)(Math.Abs(state.fp-state.fc))<=(double)(state.epsf*Math.Max(Math.Abs(state.fc), Math.Max(Math.Abs(state.fp), 1.0))) )
+                {
+                    state.repterminationtype = 1;
+                    goto lbl_40;
+                }
+            }
+            if( state.maxits>0 && state.repinneriterationscount>=state.maxits )
+            {
+                state.repterminationtype = 5;
+                goto lbl_40;
+            }
+            
+            //
+            // Clear "steepest descent" flag.
+            //
+            state.steepestdescentstep = false;
+            
+            //
+            // Smooth reset (LBFGS memory model is refreshed) or hard restart:
+            // * LBFGS model is refreshed, if line search was performed with activation of constraints
+            // * algorithm is restarted if scaled gradient decreased below GDecay
+            //
+            if( actstatus>=0 )
+            {
+                state.bufsize = 0;
+                goto lbl_39;
+            }
+            v = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.cgc[i]*state.s[i]);
+            }
+            if( (double)(Math.Sqrt(v))<(double)(gdecay*ginit) )
+            {
+                goto lbl_40;
+            }
+            goto lbl_39;
+        lbl_40:
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                state.repterminationtype = 8;
+                goto lbl_31;
+            }
+            if( state.repterminationtype!=0 )
+            {
+                
+                //
+                // Algorithm terminated
+                //
+                goto lbl_31;
+            }
+            
+            //
+            // Decrease decay coefficient. Subsequent L-BFGS stages will
+            // have more stringent stopping criteria.
+            //
+            gdecay = Math.Max(gdecay*decaycorrection, mindecay);
+            goto lbl_30;
+        lbl_31:
+            sactivesets.sasstopoptimization(state.sas, _params);
+            state.repouteriterationscount = 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] = mcinfo;
+            state.rstate.ia[5] = actstatus;
+            state.rstate.ia[6] = itidx;
+            state.rstate.ba[0] = b;
+            state.rstate.ra[0] = v;
+            state.rstate.ra[1] = vv;
+            state.rstate.ra[2] = v0;
+            state.rstate.ra[3] = penalty;
+            state.rstate.ra[4] = ginit;
+            state.rstate.ra[5] = gdecay;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This  function  activates/deactivates verification  of  the  user-supplied
+        analytic gradient.
+
+        Upon  activation  of  this  option  OptGuard  integrity  checker  performs
+        numerical differentiation of your target function  at  the  initial  point
+        (note: future versions may also perform check  at  the  final  point)  and
+        compares numerical gradient with analytic one provided by you.
+
+        If difference is too large, an error flag is set and optimization  session
+        continues. After optimization session is over, you can retrieve the report
+        which  stores  both  gradients  and  specific  components  highlighted  as
+        suspicious by the OptGuard.
+
+        The primary OptGuard report can be retrieved with minbleicoptguardresults().
+
+        IMPORTANT: gradient check is a high-overhead option which  will  cost  you
+                   about 3*N additional function evaluations. In many cases it may
+                   cost as much as the rest of the optimization session.
+                   
+                   YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO
+                   CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY.
+
+        NOTE: unlike previous incarnation of the gradient checking code,  OptGuard
+              does NOT interrupt optimization even if it discovers bad gradient.
+              
+        INPUT PARAMETERS:
+            State       -   structure used to store algorithm state
+            TestStep    -   verification step used for numerical differentiation:
+                            * TestStep=0 turns verification off
+                            * TestStep>0 activates verification
+                            You should carefully choose TestStep. Value  which  is
+                            too large (so large that  function  behavior  is  non-
+                            cubic at this scale) will lead  to  false  alarms. Too
+                            short step will result in rounding  errors  dominating
+                            numerical derivative.
+                            
+                            You may use different step for different parameters by
+                            means of setting scale with minbleicsetscale().
+
+        === EXPLANATION ==========================================================                    
+
+        In order to verify gradient algorithm performs following steps:
+          * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i],
+            where X[i] is i-th component of the initial point and S[i] is a  scale
+            of i-th parameter
+          * F(X) is evaluated at these trial points
+          * we perform one more evaluation in the middle point of the interval
+          * we  build  cubic  model using function values and derivatives at trial
+            points and we compare its prediction with actual value in  the  middle
+            point
+
+          -- ALGLIB --
+             Copyright 15.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicoptguardgradient(minbleicstate state,
+            double teststep,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(teststep), "MinBLEICOptGuardGradient: TestStep contains NaN or INF");
+            alglib.ap.assert((double)(teststep)>=(double)(0), "MinBLEICOptGuardGradient: invalid argument TestStep(TestStep<0)");
+            state.teststep = teststep;
+        }
+
+
+        /*************************************************************************
+        This  function  activates/deactivates nonsmoothness monitoring  option  of
+        the  OptGuard  integrity  checker. Smoothness  monitor  silently  observes
+        solution process and tries to detect ill-posed problems, i.e. ones with:
+        a) discontinuous target function (non-C0)
+        b) nonsmooth     target function (non-C1)
+
+        Smoothness monitoring does NOT interrupt optimization  even if it suspects
+        that your problem is nonsmooth. It just sets corresponding  flags  in  the
+        OptGuard report which can be retrieved after optimization is over.
+
+        Smoothness monitoring is a moderate overhead option which often adds  less
+        than 1% to the optimizer running time. Thus, you can use it even for large
+        scale problems.
+
+        NOTE: OptGuard does  NOT  guarantee  that  it  will  always  detect  C0/C1
+              continuity violations.
+              
+              First, minor errors are hard to  catch - say, a 0.0001 difference in
+              the model values at two sides of the gap may be due to discontinuity
+              of the model - or simply because the model has changed.
+              
+              Second, C1-violations  are  especially  difficult  to  detect  in  a
+              noninvasive way. The optimizer usually  performs  very  short  steps
+              near the nonsmoothness, and differentiation  usually   introduces  a
+              lot of numerical noise.  It  is  hard  to  tell  whether  some  tiny
+              discontinuity in the slope is due to real nonsmoothness or just  due
+              to numerical noise alone.
+              
+              Our top priority was to avoid false positives, so in some rare cases
+              minor errors may went unnoticed (however, in most cases they can  be
+              spotted with restart from different initial point).
+
+        INPUT PARAMETERS:
+            state   -   algorithm state
+            level   -   monitoring level:
+                        * 0 - monitoring is disabled
+                        * 1 - noninvasive low-overhead monitoring; function values
+                              and/or gradients are recorded, but OptGuard does not
+                              try to perform additional evaluations  in  order  to
+                              get more information about suspicious locations.
+          
+        === EXPLANATION ==========================================================
+
+        One major source of headache during optimization  is  the  possibility  of
+        the coding errors in the target function/constraints (or their gradients).
+        Such  errors   most   often   manifest   themselves  as  discontinuity  or
+        nonsmoothness of the target/constraints.
+
+        Another frequent situation is when you try to optimize something involving
+        lots of min() and max() operations, i.e. nonsmooth target. Although not  a
+        coding error, it is nonsmoothness anyway - and smooth  optimizers  usually
+        stop right after encountering nonsmoothness, well before reaching solution.
+
+        OptGuard integrity checker helps you to catch such situations: it monitors
+        function values/gradients being passed  to  the  optimizer  and  tries  to
+        errors. Upon discovering suspicious pair of points it  raises  appropriate
+        flag (and allows you to continue optimization). When optimization is done,
+        you can study OptGuard result.
+
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicoptguardsmoothness(minbleicstate state,
+            int level,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(level==0 || level==1, "MinBLEICOptGuardSmoothness: unexpected value of level parameter");
+            state.smoothnessguardlevel = level;
+        }
+
+
+        /*************************************************************************
+        Results of OptGuard integrity check, should be called  after  optimization
+        session is over.
+
+        === PRIMARY REPORT =======================================================
+
+        OptGuard performs several checks which are intended to catch common errors
+        in the implementation of nonlinear function/gradient:
+        * incorrect analytic gradient
+        * discontinuous (non-C0) target functions (constraints)
+        * nonsmooth     (non-C1) target functions (constraints)
+
+        Each of these checks is activated with appropriate function:
+        * minbleicoptguardgradient() for gradient verification
+        * minbleicoptguardsmoothness() for C0/C1 checks
+
+        Following flags are set when these errors are suspected:
+        * rep.badgradsuspected, and additionally:
+          * rep.badgradvidx for specific variable (gradient element) suspected
+          * rep.badgradxbase, a point where gradient is tested
+          * rep.badgraduser, user-provided gradient  (stored  as  2D  matrix  with
+            single row in order to make  report  structure  compatible  with  more
+            complex optimizers like MinNLC or MinLM)
+          * rep.badgradnum,   reference    gradient    obtained    via   numerical
+            differentiation (stored as  2D matrix with single row in order to make
+            report structure compatible with more complex optimizers  like  MinNLC
+            or MinLM)
+        * rep.nonc0suspected
+        * rep.nonc1suspected
+
+        === ADDITIONAL REPORTS/LOGS ==============================================
+            
+        Several different tests are performed to catch C0/C1 errors, you can  find
+        out specific test signaled error by looking to:
+        * rep.nonc0test0positive, for non-C0 test #0
+        * rep.nonc1test0positive, for non-C1 test #0
+        * rep.nonc1test1positive, for non-C1 test #1
+
+        Additional information (including line search logs)  can  be  obtained  by
+        means of:
+        * minbleicoptguardnonc1test0results()
+        * minbleicoptguardnonc1test1results()
+        which return detailed error reports, specific points where discontinuities
+        were found, and so on.
+
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            rep     -   generic OptGuard report;  more  detailed  reports  can  be
+                        retrieved with other functions.
+
+        NOTE: false negatives (nonsmooth problems are not identified as  nonsmooth
+              ones) are possible although unlikely.
+              
+              The reason  is  that  you  need  to  make several evaluations around
+              nonsmoothness  in  order  to  accumulate  enough  information  about
+              function curvature. Say, if you start right from the nonsmooth point,
+              optimizer simply won't get enough data to understand what  is  going
+              wrong before it terminates due to abrupt changes in the  derivative.
+              It is also  possible  that  "unlucky"  step  will  move  us  to  the
+              termination too quickly.
+              
+              Our current approach is to have less than 0.1%  false  negatives  in
+              our test examples  (measured  with  multiple  restarts  from  random
+              points), and to have exactly 0% false positives.
+           
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicoptguardresults(minbleicstate state,
+            optguardapi.optguardreport rep,
+            alglib.xparams _params)
+        {
+            optserv.smoothnessmonitorexportreport(state.smonitor, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Detailed results of the OptGuard integrity check for nonsmoothness test #0
+
+        Nonsmoothness (non-C1) test #0 studies  function  values  (not  gradient!)
+        obtained during line searches and monitors  behavior  of  the  directional
+        derivative estimate.
+
+        This test is less powerful than test #1, but it does  not  depend  on  the
+        gradient values and thus it is more robust against artifacts introduced by
+        numerical differentiation.
+
+        Two reports are returned:
+        * a "strongest" one, corresponding  to  line   search  which  had  highest
+          value of the nonsmoothness indicator
+        * a "longest" one, corresponding to line search which  had  more  function
+          evaluations, and thus is more detailed
+
+        In both cases following fields are returned:
+
+        * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+          did not notice anything (in the latter cases fields below are empty).
+        * x0[], d[] - arrays of length N which store initial point  and  direction
+          for line search (d[] can be normalized, but does not have to)
+        * stp[], f[] - arrays of length CNT which store step lengths and  function
+          values at these points; f[i] is evaluated in x0+stp[i]*d.
+        * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+          between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+          with  most  likely  position  of  the  violation  between  stpidxa+1 and
+          stpidxa+2.
+          
+        ==========================================================================
+        = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+        =                   see where C1 continuity is violated.
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            strrep  -   C1 test #0 "strong" report
+            lngrep  -   C1 test #0 "long" report
+            
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicoptguardnonc1test0results(minbleicstate state,
+            optguardapi.optguardnonc1test0report strrep,
+            optguardapi.optguardnonc1test0report lngrep,
+            alglib.xparams _params)
+        {
+            optguardapi.smoothnessmonitorexportc1test0report(state.smonitor.nonc1test0strrep, state.lastscaleused, strrep, _params);
+            optguardapi.smoothnessmonitorexportc1test0report(state.smonitor.nonc1test0lngrep, state.lastscaleused, lngrep, _params);
+        }
+
+
+        /*************************************************************************
+        Detailed results of the OptGuard integrity check for nonsmoothness test #1
+
+        Nonsmoothness (non-C1)  test  #1  studies  individual  components  of  the
+        gradient computed during line search.
+
+        When precise analytic gradient is provided this test is more powerful than
+        test #0  which  works  with  function  values  and  ignores  user-provided
+        gradient.  However,  test  #0  becomes  more   powerful   when   numerical
+        differentiation is employed (in such cases test #1 detects  higher  levels
+        of numerical noise and becomes too conservative).
+
+        This test also tells specific components of the gradient which violate  C1
+        continuity, which makes it more informative than #0, which just tells that
+        continuity is violated.
+
+        Two reports are returned:
+        * a "strongest" one, corresponding  to  line   search  which  had  highest
+          value of the nonsmoothness indicator
+        * a "longest" one, corresponding to line search which  had  more  function
+          evaluations, and thus is more detailed
+
+        In both cases following fields are returned:
+
+        * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+          did not notice anything (in the latter cases fields below are empty).
+        * vidx - is an index of the variable in [0,N) with nonsmooth derivative
+        * x0[], d[] - arrays of length N which store initial point  and  direction
+          for line search (d[] can be normalized, but does not have to)
+        * stp[], g[] - arrays of length CNT which store step lengths and  gradient
+          values at these points; g[i] is evaluated in  x0+stp[i]*d  and  contains
+          vidx-th component of the gradient.
+        * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+          between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+          with  most  likely  position  of  the  violation  between  stpidxa+1 and
+          stpidxa+2.
+          
+        ==========================================================================
+        = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+        =                   see where C1 continuity is violated.
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            strrep  -   C1 test #1 "strong" report
+            lngrep  -   C1 test #1 "long" report
+            
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicoptguardnonc1test1results(minbleicstate state,
+            optguardapi.optguardnonc1test1report strrep,
+            optguardapi.optguardnonc1test1report lngrep,
+            alglib.xparams _params)
+        {
+            optguardapi.smoothnessmonitorexportc1test1report(state.smonitor.nonc1test1strrep, state.lastscaleused, strrep, _params);
+            optguardapi.smoothnessmonitorexportc1test1report(state.smonitor.nonc1test1lngrep, state.lastscaleused, lngrep, _params);
+        }
+
+
+        /*************************************************************************
+        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:
+                        * -8    internal integrity control  detected  infinite or
+                                NAN   values   in   function/gradient.   Abnormal
+                                termination signalled.
+                        * -3   inconsistent constraints. Feasible point is
+                               either nonexistent or too hard to find. Try to
+                               restart optimizer with better initial approximation
+                        *  1   relative function improvement is no more than EpsF.
+                        *  2   scaled step is no more than EpsX.
+                        *  4   scaled gradient norm is no more than EpsG.
+                        *  5   MaxIts steps was taken
+                        *  8   terminated by user who called minbleicrequesttermination().
+                               X contains point which was "current accepted"  when
+                               termination request was submitted.
+                        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,
+            alglib.xparams _params)
+        {
+            x = new double[0];
+
+            minbleicresultsbuf(state, ref x, rep, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            if( alglib.ap.len(x)<state.nmain )
+            {
+                x = new double[state.nmain];
+            }
+            rep.iterationscount = state.repinneriterationscount;
+            rep.inneriterationscount = state.repinneriterationscount;
+            rep.outeriterationscount = state.repouteriterationscount;
+            rep.nfev = state.repnfev;
+            rep.varidx = state.repvaridx;
+            rep.terminationtype = state.repterminationtype;
+            if( state.repterminationtype>0 )
+            {
+                for(i_=0; i_<=state.nmain-1;i_++)
+                {
+                    x[i_] = state.sas.xc[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,
+            alglib.xparams _params)
+        {
+            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, _params), "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[6+1];
+            state.rstate.ba = new bool[0+1];
+            state.rstate.ra = new double[5+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state, _params);
+            sactivesets.sasstopoptimization(state.sas, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine submits request for termination of running  optimizer.  It
+        should be called from user-supplied callback when user decides that it  is
+        time to "smoothly" terminate optimization process.  As  result,  optimizer
+        stops at point which was "current accepted" when termination  request  was
+        submitted and returns error code 8 (successful termination).
+
+        INPUT PARAMETERS:
+            State   -   optimizer structure
+
+        NOTE: after  request  for  termination  optimizer  may   perform   several
+              additional calls to user-supplied callbacks. It does  NOT  guarantee
+              to stop immediately - it just guarantees that these additional calls
+              will be discarded later.
+
+        NOTE: calling this function on optimizer which is NOT running will have no
+              effect.
+              
+        NOTE: multiple calls to this function are possible. First call is counted,
+              subsequent calls are silently ignored.
+
+          -- ALGLIB --
+             Copyright 08.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicrequesttermination(minbleicstate state,
+            alglib.xparams _params)
+        {
+            state.userterminationneeded = true;
+        }
+
+
+        /*************************************************************************
+        This subroutine finalizes internal structures after emergency  termination
+        from State.LSStart report (see comments on MinBLEICState for more information).
+
+        INPUT PARAMETERS:
+            State   -   structure after exit from LSStart report
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbleicemergencytermination(minbleicstate state,
+            alglib.xparams _params)
+        {
+            sactivesets.sasstopoptimization(state.sas, _params);
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forget to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(minbleicstate state,
+            alglib.xparams _params)
+        {
+            state.needf = false;
+            state.needfg = false;
+            state.xupdated = false;
+            state.lsstart = false;
+        }
+
+
+        /*************************************************************************
+        Internal initialization subroutine
+        *************************************************************************/
+        private static void minbleicinitinternal(int n,
+            double[] x,
+            double diffstep,
+            minbleicstate state,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double[,] c = new double[0,0];
+            int[] ct = new int[0];
+
+            
+            //
+            // Initialize
+            //
+            state.teststep = 0;
+            state.smoothnessguardlevel = 0;
+            optserv.smoothnessmonitorinit(state.smonitor, 0, 0, false, _params);
+            state.nmain = n;
+            state.diffstep = diffstep;
+            sactivesets.sasinit(n, state.sas, _params);
+            state.bndl = new double[n];
+            state.hasbndl = new bool[n];
+            state.bndu = new double[n];
+            state.hasbndu = new bool[n];
+            state.xstart = new double[n];
+            state.cgc = new double[n];
+            state.ugc = new double[n];
+            state.xn = new double[n];
+            state.cgn = new double[n];
+            state.ugn = new double[n];
+            state.xp = new double[n];
+            state.d = new double[n];
+            state.s = new double[n];
+            state.invs = new double[n];
+            state.lastscaleused = new double[n];
+            state.x = new double[n];
+            state.g = new double[n];
+            state.work = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                state.bndl[i] = Double.NegativeInfinity;
+                state.hasbndl[i] = false;
+                state.bndu[i] = Double.PositiveInfinity;
+                state.hasbndu[i] = false;
+                state.s[i] = 1.0;
+                state.invs[i] = 1.0;
+                state.lastscaleused[i] = 1.0;
+            }
+            minbleicsetlc(state, c, ct, 0, _params);
+            minbleicsetcond(state, 0.0, 0.0, 0.0, 0, _params);
+            minbleicsetxrep(state, false, _params);
+            minbleicsetdrep(state, false, _params);
+            minbleicsetstpmax(state, 0.0, _params);
+            minbleicsetprecdefault(state, _params);
+            minbleicrestartfrom(state, x, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine updates estimate of the good step length given:
+        1) previous estimate
+        2) new length of the good step
+
+        It makes sure that estimate does not change too rapidly - ratio of new and
+        old estimates will be at least 0.01, at most 100.0
+
+        In case previous estimate of good step is zero (no estimate), new estimate
+        is used unconditionally.
+
+          -- ALGLIB --
+             Copyright 16.01.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void updateestimateofgoodstep(ref double estimate,
+            double newstep,
+            alglib.xparams _params)
+        {
+            if( (double)(estimate)==(double)(0) )
+            {
+                estimate = newstep;
+                return;
+            }
+            if( (double)(newstep)<(double)(estimate*0.01) )
+            {
+                estimate = estimate*0.01;
+                return;
+            }
+            if( (double)(newstep)>(double)(estimate*100) )
+            {
+                estimate = estimate*100;
+                return;
+            }
+            estimate = newstep;
+        }
+
+
+        /*************************************************************************
+        This subroutine estimates relative feasibility error of the point.
+
+        INPUT PARAMETERS:
+            X       -   current point (unscaled)
+            S       -   scale vector
+            N       -   dimensionality
+            CLEIC   -   array[NEC+NIC,N+1], constraint matrix, may be unnormalized
+            NEC     -   number of equality constraints (come first)
+            NIC     -   number of inequality constraints (come last)
+            
+        RESULT
+            feasibility error, good value is ~1E-16...1E-14
+
+          -- ALGLIB --
+             Copyright 16.01.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static double feasibilityerror(double[] x,
+            double[] s,
+            int n,
+            double[,] cleic,
+            int nec,
+            int nic,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double vc = 0;
+            double vx = 0;
+
+            result = 0;
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                v = -cleic[i,n];
+                v0 = 0;
+                v1 = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    vc = cleic[i,j]*s[j];
+                    vx = x[j]/s[j];
+                    v = v+vc*vx;
+                    v0 = v0+math.sqr(vc);
+                    v1 = v1+math.sqr(vx);
+                }
+                v0 = apserv.coalesce(Math.Sqrt(v0), 1, _params);
+                v1 = Math.Max(Math.Sqrt(v1), 1);
+                if( i<nec )
+                {
+                    v = Math.Abs(v);
+                }
+                else
+                {
+                    v = Math.Max(v, 0);
+                }
+                result = Math.Max(result, v/(v0*v1));
+            }
+            return result;
+        }
+
+
+    }
+    public class qpbleicsolver
+    {
+        /*************************************************************************
+        This object stores settings for QPBLEIC solver.
+        It must be initialized with QPBLEICLoadDefaults().
+        After initialization you may change settings.
+        *************************************************************************/
+        public class qpbleicsettings : apobject
+        {
+            public double epsg;
+            public double epsf;
+            public double epsx;
+            public int maxits;
+            public qpbleicsettings()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                qpbleicsettings _result = new qpbleicsettings();
+                _result.epsg = epsg;
+                _result.epsf = epsf;
+                _result.epsx = epsx;
+                _result.maxits = maxits;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This object stores temporaries used by QuickQP solver.
+        *************************************************************************/
+        public class qpbleicbuffers : apobject
+        {
+            public minbleic.minbleicstate solver;
+            public minbleic.minbleicreport solverrep;
+            public double[] tmp0;
+            public double[] tmp1;
+            public int[] tmpi;
+            public int repinneriterationscount;
+            public int repouteriterationscount;
+            public qpbleicbuffers()
+            {
+                init();
+            }
+            public override void init()
+            {
+                solver = new minbleic.minbleicstate();
+                solverrep = new minbleic.minbleicreport();
+                tmp0 = new double[0];
+                tmp1 = new double[0];
+                tmpi = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                qpbleicbuffers _result = new qpbleicbuffers();
+                _result.solver = (minbleic.minbleicstate)solver.make_copy();
+                _result.solverrep = (minbleic.minbleicreport)solverrep.make_copy();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmp1 = (double[])tmp1.Clone();
+                _result.tmpi = (int[])tmpi.Clone();
+                _result.repinneriterationscount = repinneriterationscount;
+                _result.repouteriterationscount = repouteriterationscount;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        This function initializes QPBLEICSettings structure with default settings.
+
+        Newly created structure MUST be initialized by default settings  -  or  by
+        copy of the already initialized structure.
+
+          -- ALGLIB --
+             Copyright 14.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void qpbleicloaddefaults(int nmain,
+            qpbleicsettings s,
+            alglib.xparams _params)
+        {
+            s.epsg = 0.0;
+            s.epsf = 0.0;
+            s.epsx = 1.0E-6;
+            s.maxits = 0;
+        }
+
+
+        /*************************************************************************
+        This function initializes QPBLEICSettings  structure  with  copy  of  another,
+        already initialized structure.
+
+          -- ALGLIB --
+             Copyright 14.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void qpbleiccopysettings(qpbleicsettings src,
+            qpbleicsettings dst,
+            alglib.xparams _params)
+        {
+            dst.epsg = src.epsg;
+            dst.epsf = src.epsf;
+            dst.epsx = src.epsx;
+            dst.maxits = src.maxits;
+        }
+
+
+        /*************************************************************************
+        This function runs QPBLEIC solver; it returns after optimization   process
+        was completed. Following QP problem is solved:
+
+            min(0.5*(x-x_origin)'*A*(x-x_origin)+b'*(x-x_origin))
+            
+        subject to boundary constraints.
+
+        INPUT PARAMETERS:
+            AC          -   for dense problems (AKind=0), A-term of CQM object
+                            contains system matrix. Other terms are unspecified
+                            and should not be referenced.
+            SparseAC    -   for sparse problems (AKind=1
+            AKind       -   sparse matrix format:
+                            * 0 for dense matrix
+                            * 1 for sparse matrix
+            SparseUpper -   which triangle of SparseAC stores matrix  -  upper  or
+                            lower one (for dense matrices this  parameter  is  not
+                            actual).
+            AbsASum     -   SUM(|A[i,j]|)
+            AbsASum2    -   SUM(A[i,j]^2)
+            BC          -   linear term, array[NC]
+            BndLC       -   lower bound, array[NC]
+            BndUC       -   upper bound, array[NC]
+            SC          -   scale vector, array[NC]:
+                            * I-th element contains scale of I-th variable,
+                            * SC[I]>0
+            XOriginC    -   origin term, array[NC]. Can be zero.
+            NC          -   number of variables in the  original  formulation  (no
+                            slack variables).
+            CLEICC      -   linear equality/inequality constraints. Present version
+                            of this function does NOT provide  publicly  available
+                            support for linear constraints. This feature  will  be
+                            introduced in the future versions of the function.
+            NEC, NIC    -   number of equality/inequality constraints.
+                            MUST BE ZERO IN THE CURRENT VERSION!!!
+            Settings    -   QPBLEICSettings object initialized by one of the initialization
+                            functions.
+            SState      -   object which stores temporaries:
+                            * if uninitialized object was passed, FirstCall parameter MUST
+                              be set to True; object will be automatically initialized by the
+                              function, and FirstCall will be set to False.
+                            * if FirstCall=False, it is assumed that this parameter was already
+                              initialized by previous call to this function with same
+                              problem dimensions (variable count N).
+            FirstCall   -   whether it is first call of this function for this specific
+                            instance of SState, with this number of variables N specified.
+            XS          -   initial point, array[NC]
+            
+            
+        OUTPUT PARAMETERS:
+            XS          -   last point
+            FirstCall   -   uncondtionally set to False
+            TerminationType-termination type:
+                            *
+                            *
+                            *
+
+          -- ALGLIB --
+             Copyright 14.05.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void qpbleicoptimize(cqmodels.convexquadraticmodel a,
+            sparse.sparsematrix sparsea,
+            int akind,
+            bool sparseaupper,
+            double absasum,
+            double absasum2,
+            double[] b,
+            double[] bndl,
+            double[] bndu,
+            double[] s,
+            double[] xorigin,
+            int n,
+            double[,] cleic,
+            int nec,
+            int nic,
+            qpbleicsettings settings,
+            qpbleicbuffers sstate,
+            ref bool firstcall,
+            ref double[] xs,
+            ref int terminationtype,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double d2 = 0;
+            double d1 = 0;
+            double d0 = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double md = 0;
+            double mx = 0;
+            double mb = 0;
+            int d1est = 0;
+            int d2est = 0;
+            int i_ = 0;
+
+            terminationtype = 0;
+
+            alglib.ap.assert(akind==0 || akind==1, "QPBLEICOptimize: unexpected AKind");
+            sstate.repinneriterationscount = 0;
+            sstate.repouteriterationscount = 0;
+            terminationtype = 0;
+            
+            //
+            // Prepare solver object, if needed
+            //
+            if( firstcall )
+            {
+                minbleic.minbleiccreate(n, xs, sstate.solver, _params);
+                firstcall = false;
+            }
+            
+            //
+            // Prepare max(|B|)
+            //
+            mb = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                mb = Math.Max(mb, Math.Abs(b[i]));
+            }
+            
+            //
+            // Temporaries
+            //
+            apserv.ivectorsetlengthatleast(ref sstate.tmpi, nec+nic, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.tmp0, n, _params);
+            apserv.rvectorsetlengthatleast(ref sstate.tmp1, n, _params);
+            for(i=0; i<=nec-1; i++)
+            {
+                sstate.tmpi[i] = 0;
+            }
+            for(i=0; i<=nic-1; i++)
+            {
+                sstate.tmpi[nec+i] = -1;
+            }
+            minbleic.minbleicsetlc(sstate.solver, cleic, sstate.tmpi, nec+nic, _params);
+            minbleic.minbleicsetbc(sstate.solver, bndl, bndu, _params);
+            minbleic.minbleicsetdrep(sstate.solver, true, _params);
+            minbleic.minbleicsetcond(sstate.solver, math.minrealnumber, 0.0, 0.0, settings.maxits, _params);
+            minbleic.minbleicsetscale(sstate.solver, s, _params);
+            minbleic.minbleicsetprecscale(sstate.solver, _params);
+            minbleic.minbleicrestartfrom(sstate.solver, xs, _params);
+            while( minbleic.minbleiciteration(sstate.solver, _params) )
+            {
+                
+                //
+                // Line search started
+                //
+                if( sstate.solver.lsstart )
+                {
+                    
+                    //
+                    // Iteration counters:
+                    // * inner iterations count is increased on every line search
+                    // * outer iterations count is increased only at steepest descent line search
+                    //
+                    apserv.inc(ref sstate.repinneriterationscount, _params);
+                    if( sstate.solver.steepestdescentstep )
+                    {
+                        apserv.inc(ref sstate.repouteriterationscount, _params);
+                    }
+                    
+                    //
+                    // Build quadratic model of F along descent direction:
+                    //
+                    //     F(x+alpha*d) = D2*alpha^2 + D1*alpha + D0
+                    //
+                    // Calculate estimates of linear and quadratic term
+                    // (term magnitude is compared with magnitude of numerical errors)
+                    //
+                    d0 = sstate.solver.f;
+                    d1 = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        d1 += sstate.solver.d[i_]*sstate.solver.g[i_];
+                    }
+                    d2 = 0;
+                    if( akind==0 )
+                    {
+                        d2 = cqmodels.cqmxtadx2(a, sstate.solver.d, sstate.tmp0, _params);
+                    }
+                    if( akind==1 )
+                    {
+                        sparse.sparsesmv(sparsea, sparseaupper, sstate.solver.d, ref sstate.tmp0, _params);
+                        d2 = 0.0;
+                        for(i=0; i<=n-1; i++)
+                        {
+                            d2 = d2+sstate.solver.d[i]*sstate.tmp0[i];
+                        }
+                        d2 = 0.5*d2;
+                    }
+                    mx = 0.0;
+                    md = 0.0;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        mx = Math.Max(mx, Math.Abs(sstate.solver.x[i]));
+                        md = Math.Max(md, Math.Abs(sstate.solver.d[i]));
+                    }
+                    optserv.estimateparabolicmodel(absasum, absasum2, mx, mb, md, d1, d2, ref d1est, ref d2est, _params);
+                    
+                    //
+                    // Tests for "normal" convergence.
+                    //
+                    // This line search may be started from steepest descent
+                    // stage (stage 2) or from L-BFGS stage (stage 3) of the
+                    // BLEIC algorithm. Depending on stage type, different
+                    // checks are performed.
+                    //
+                    // Say, L-BFGS stage is an equality-constrained refinement
+                    // stage of BLEIC. This stage refines current iterate
+                    // under "frozen" equality constraints. We can terminate
+                    // iterations at this stage only when we encounter
+                    // unconstrained direction of negative curvature. In all
+                    // other cases (say, when constrained gradient is zero)
+                    // we should not terminate algorithm because everything may
+                    // change after de-activating presently active constraints.
+                    //
+                    // Tests for convergence are performed only at "steepest descent" stage
+                    // of the BLEIC algorithm, and only when function is non-concave
+                    // (D2 is positive or approximately zero) along direction D.
+                    //
+                    // NOTE: we do not test iteration count (MaxIts) here, because
+                    //       this stopping condition is tested by BLEIC itself.
+                    //
+                    if( sstate.solver.steepestdescentstep && d2est>=0 )
+                    {
+                        if( d1est>=0 )
+                        {
+                            
+                            //
+                            // "Emergency" stopping condition: D is non-descent direction.
+                            // Sometimes it is possible because of numerical noise in the
+                            // target function.
+                            //
+                            terminationtype = 4;
+                            for(i=0; i<=n-1; i++)
+                            {
+                                xs[i] = sstate.solver.x[i];
+                            }
+                            break;
+                        }
+                        if( d2est>0 )
+                        {
+                            
+                            //
+                            // Stopping condition #4 - gradient norm is small:
+                            //
+                            // 1. rescale State.Solver.D and State.Solver.G according to
+                            //    current scaling, store results to Tmp0 and Tmp1.
+                            // 2. Normalize Tmp0 (scaled direction vector).
+                            // 3. compute directional derivative (in scaled variables),
+                            //    which is equal to DOTPRODUCT(Tmp0,Tmp1).
+                            //
+                            v = 0;
+                            for(i=0; i<=n-1; i++)
+                            {
+                                sstate.tmp0[i] = sstate.solver.d[i]/s[i];
+                                sstate.tmp1[i] = sstate.solver.g[i]*s[i];
+                                v = v+math.sqr(sstate.tmp0[i]);
+                            }
+                            alglib.ap.assert((double)(v)>(double)(0), "QPBLEICOptimize: inernal errror (scaled direction is zero)");
+                            v = 1/Math.Sqrt(v);
+                            for(i_=0; i_<=n-1;i_++)
+                            {
+                                sstate.tmp0[i_] = v*sstate.tmp0[i_];
+                            }
+                            v = 0.0;
+                            for(i_=0; i_<=n-1;i_++)
+                            {
+                                v += sstate.tmp0[i_]*sstate.tmp1[i_];
+                            }
+                            if( (double)(Math.Abs(v))<=(double)(settings.epsg) )
+                            {
+                                terminationtype = 4;
+                                for(i=0; i<=n-1; i++)
+                                {
+                                    xs[i] = sstate.solver.x[i];
+                                }
+                                break;
+                            }
+                            
+                            //
+                            // Stopping condition #1 - relative function improvement is small:
+                            //
+                            // 1. calculate steepest descent step:   V = -D1/(2*D2)
+                            // 2. calculate function change:         V1= D2*V^2 + D1*V
+                            // 3. stop if function change is small enough
+                            //
+                            v = -(d1/(2*d2));
+                            v1 = d2*v*v+d1*v;
+                            if( (double)(Math.Abs(v1))<=(double)(settings.epsf*Math.Max(d0, 1.0)) )
+                            {
+                                terminationtype = 1;
+                                for(i=0; i<=n-1; i++)
+                                {
+                                    xs[i] = sstate.solver.x[i];
+                                }
+                                break;
+                            }
+                            
+                            //
+                            // Stopping condition #2 - scaled step is small:
+                            //
+                            // 1. calculate step multiplier V0 (step itself is D*V0)
+                            // 2. calculate scaled step length V
+                            // 3. stop if step is small enough
+                            //
+                            v0 = -(d1/(2*d2));
+                            v = 0;
+                            for(i=0; i<=n-1; i++)
+                            {
+                                v = v+math.sqr(v0*sstate.solver.d[i]/s[i]);
+                            }
+                            if( (double)(Math.Sqrt(v))<=(double)(settings.epsx) )
+                            {
+                                terminationtype = 2;
+                                for(i=0; i<=n-1; i++)
+                                {
+                                    xs[i] = sstate.solver.x[i];
+                                }
+                                break;
+                            }
+                        }
+                    }
+                    
+                    //
+                    // Test for unconstrained direction of negative curvature
+                    //
+                    if( (d2est<0 || (d2est==0 && d1est<0)) && !sstate.solver.boundedstep )
+                    {
+                        
+                        //
+                        // Function is unbounded from below:
+                        // * function will decrease along D, i.e. either:
+                        //   * D2<0
+                        //   * D2=0 and D1<0
+                        // * step is unconstrained
+                        //
+                        // If these conditions are true, we abnormally terminate QP
+                        // algorithm with return code -4 (we can do so at any stage
+                        // of BLEIC - whether it is L-BFGS or steepest descent one).
+                        //
+                        terminationtype = -4;
+                        for(i=0; i<=n-1; i++)
+                        {
+                            xs[i] = sstate.solver.x[i];
+                        }
+                        break;
+                    }
+                    
+                    //
+                    // Suggest new step (only if D1 is negative far away from zero,
+                    // D2 is positive far away from zero).
+                    //
+                    if( d1est<0 && d2est>0 )
+                    {
+                        sstate.solver.stp = apserv.safeminposrv(-d1, 2*d2, sstate.solver.curstpmax, _params);
+                    }
+                }
+                
+                //
+                // Gradient evaluation
+                //
+                if( sstate.solver.needfg )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        sstate.tmp0[i] = sstate.solver.x[i]-xorigin[i];
+                    }
+                    if( akind==0 )
+                    {
+                        cqmodels.cqmadx(a, sstate.tmp0, ref sstate.tmp1, _params);
+                    }
+                    if( akind==1 )
+                    {
+                        sparse.sparsesmv(sparsea, sparseaupper, sstate.tmp0, ref sstate.tmp1, _params);
+                    }
+                    v0 = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v0 += sstate.tmp0[i_]*sstate.tmp1[i_];
+                    }
+                    v1 = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        v1 += sstate.tmp0[i_]*b[i_];
+                    }
+                    sstate.solver.f = 0.5*v0+v1;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        sstate.solver.g[i_] = sstate.tmp1[i_];
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        sstate.solver.g[i_] = sstate.solver.g[i_] + b[i_];
+                    }
+                }
+            }
+            if( terminationtype==0 )
+            {
+                
+                //
+                // BLEIC optimizer was terminated by one of its inner stopping
+                // conditions. Usually it is iteration counter (if such
+                // stopping condition was specified by user).
+                //
+                minbleic.minbleicresultsbuf(sstate.solver, ref xs, sstate.solverrep, _params);
+                terminationtype = sstate.solverrep.terminationtype;
+            }
+            else
+            {
+                
+                //
+                // BLEIC optimizer was terminated in "emergency" mode by QP
+                // solver.
+                //
+                // NOTE: such termination is "emergency" only when viewed from
+                //       BLEIC's position. QP solver sees such termination as
+                //       routine one, triggered by QP's stopping criteria.
+                //
+                minbleic.minbleicemergencytermination(sstate.solver, _params);
+            }
+        }
+
+
+    }
+    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 : apobject
+        {
+            public int n;
+            public qqpsolver.qqpsettings qqpsettingsuser;
+            public qpbleicsolver.qpbleicsettings qpbleicsettingsuser;
+            public qpdenseaulsolver.qpdenseaulsettings qpdenseaulsettingsuser;
+            public bool dbgskipconstraintnormalization;
+            public int algokind;
+            public int akind;
+            public cqmodels.convexquadraticmodel a;
+            public sparse.sparsematrix sparsea;
+            public bool sparseaupper;
+            public double absamax;
+            public double absasum;
+            public double absasum2;
+            public double[] b;
+            public double[] bndl;
+            public double[] bndu;
+            public int stype;
+            public double[] s;
+            public bool[] havebndl;
+            public bool[] havebndu;
+            public double[] xorigin;
+            public double[] startx;
+            public bool havex;
+            public double[,] cleic;
+            public int nec;
+            public int nic;
+            public sparse.sparsematrix scleic;
+            public int snec;
+            public int snic;
+            public double[] xs;
+            public int repinneriterationscount;
+            public int repouteriterationscount;
+            public int repncholesky;
+            public int repnmv;
+            public int repterminationtype;
+            public double[] effectives;
+            public double[] tmp0;
+            public double[,] ecleic;
+            public double[,] dummyr2;
+            public bool qpbleicfirstcall;
+            public qpbleicsolver.qpbleicbuffers qpbleicbuf;
+            public qqpsolver.qqpbuffers qqpbuf;
+            public qpdenseaulsolver.qpdenseaulbuffers qpdenseaulbuf;
+            public minqpstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                qqpsettingsuser = new qqpsolver.qqpsettings();
+                qpbleicsettingsuser = new qpbleicsolver.qpbleicsettings();
+                qpdenseaulsettingsuser = new qpdenseaulsolver.qpdenseaulsettings();
+                a = new cqmodels.convexquadraticmodel();
+                sparsea = new sparse.sparsematrix();
+                b = new double[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                s = new double[0];
+                havebndl = new bool[0];
+                havebndu = new bool[0];
+                xorigin = new double[0];
+                startx = new double[0];
+                cleic = new double[0,0];
+                scleic = new sparse.sparsematrix();
+                xs = new double[0];
+                effectives = new double[0];
+                tmp0 = new double[0];
+                ecleic = new double[0,0];
+                dummyr2 = new double[0,0];
+                qpbleicbuf = new qpbleicsolver.qpbleicbuffers();
+                qqpbuf = new qqpsolver.qqpbuffers();
+                qpdenseaulbuf = new qpdenseaulsolver.qpdenseaulbuffers();
+            }
+            public override alglib.apobject make_copy()
+            {
+                minqpstate _result = new minqpstate();
+                _result.n = n;
+                _result.qqpsettingsuser = (qqpsolver.qqpsettings)qqpsettingsuser.make_copy();
+                _result.qpbleicsettingsuser = (qpbleicsolver.qpbleicsettings)qpbleicsettingsuser.make_copy();
+                _result.qpdenseaulsettingsuser = (qpdenseaulsolver.qpdenseaulsettings)qpdenseaulsettingsuser.make_copy();
+                _result.dbgskipconstraintnormalization = dbgskipconstraintnormalization;
+                _result.algokind = algokind;
+                _result.akind = akind;
+                _result.a = (cqmodels.convexquadraticmodel)a.make_copy();
+                _result.sparsea = (sparse.sparsematrix)sparsea.make_copy();
+                _result.sparseaupper = sparseaupper;
+                _result.absamax = absamax;
+                _result.absasum = absasum;
+                _result.absasum2 = absasum2;
+                _result.b = (double[])b.Clone();
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.stype = stype;
+                _result.s = (double[])s.Clone();
+                _result.havebndl = (bool[])havebndl.Clone();
+                _result.havebndu = (bool[])havebndu.Clone();
+                _result.xorigin = (double[])xorigin.Clone();
+                _result.startx = (double[])startx.Clone();
+                _result.havex = havex;
+                _result.cleic = (double[,])cleic.Clone();
+                _result.nec = nec;
+                _result.nic = nic;
+                _result.scleic = (sparse.sparsematrix)scleic.make_copy();
+                _result.snec = snec;
+                _result.snic = snic;
+                _result.xs = (double[])xs.Clone();
+                _result.repinneriterationscount = repinneriterationscount;
+                _result.repouteriterationscount = repouteriterationscount;
+                _result.repncholesky = repncholesky;
+                _result.repnmv = repnmv;
+                _result.repterminationtype = repterminationtype;
+                _result.effectives = (double[])effectives.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.ecleic = (double[,])ecleic.Clone();
+                _result.dummyr2 = (double[,])dummyr2.Clone();
+                _result.qpbleicfirstcall = qpbleicfirstcall;
+                _result.qpbleicbuf = (qpbleicsolver.qpbleicbuffers)qpbleicbuf.make_copy();
+                _result.qqpbuf = (qqpsolver.qqpbuffers)qqpbuf.make_copy();
+                _result.qpdenseaulbuf = (qpdenseaulsolver.qpdenseaulbuffers)qpdenseaulbuf.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        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:
+        * -9    failure of the automatic scale evaluation:  one  of  the  diagonal
+                elements of the quadratic term is non-positive.  Specify  variable
+                scales manually!
+        * -5    inappropriate solver was used:
+                * QuickQP solver for problem with general linear constraints (dense/sparse)
+        * -4    BLEIC-QP or QuickQP solver found unconstrained direction
+                of negative curvature (function is unbounded from
+                below  even  under  constraints),  no  meaningful
+                minimum can be found.
+        * -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
+        *  1..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 : apobject
+        {
+            public int inneriterationscount;
+            public int outeriterationscount;
+            public int nmv;
+            public int ncholesky;
+            public int terminationtype;
+            public minqpreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                minqpreport _result = new minqpreport();
+                _result.inneriterationscount = inneriterationscount;
+                _result.outeriterationscount = outeriterationscount;
+                _result.nmv = nmv;
+                _result.ncholesky = ncholesky;
+                _result.terminationtype = terminationtype;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+                            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.
+
+        You should also choose appropriate QP solver and set it  and  its stopping
+        criteria by means of MinQPSetAlgo??????() function. Then, you should start
+        solution process by means of MinQPOptimize() call. Solution itself can  be
+        obtained with MinQPResults() function.
+
+        Following solvers are recommended:
+        * QuickQP for dense problems with box-only constraints (or no constraints
+          at all)
+        * QP-BLEIC for dense/sparse problems with moderate (up to 50) number of
+          general linear constraints
+        * DENSE-AUL-QP for dense problems with any (small or large) number of
+          general linear constraints
+
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(n>=1, "MinQPCreate: N<1");
+            
+            //
+            // initialize QP solver
+            //
+            state.n = n;
+            state.nec = 0;
+            state.nic = 0;
+            state.snec = 0;
+            state.snic = 0;
+            state.repterminationtype = 0;
+            state.absamax = 1;
+            state.absasum = 1;
+            state.absasum2 = 1;
+            state.akind = 0;
+            state.sparseaupper = false;
+            cqmodels.cqminit(n, state.a, _params);
+            state.b = new double[n];
+            state.bndl = new double[n];
+            state.bndu = new double[n];
+            state.havebndl = new bool[n];
+            state.havebndu = new bool[n];
+            state.s = new double[n];
+            state.startx = new double[n];
+            state.xorigin = new double[n];
+            state.xs = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                state.bndl[i] = Double.NegativeInfinity;
+                state.bndu[i] = Double.PositiveInfinity;
+                state.havebndl[i] = false;
+                state.havebndu[i] = false;
+                state.b[i] = 0.0;
+                state.startx[i] = 0.0;
+                state.xorigin[i] = 0.0;
+                state.s[i] = 1.0;
+            }
+            state.stype = 0;
+            state.havex = false;
+            minqpsetalgobleic(state, 0.0, 0.0, 0.0, 0, _params);
+            qqpsolver.qqploaddefaults(n, state.qqpsettingsuser, _params);
+            qpbleicsolver.qpbleicloaddefaults(n, state.qpbleicsettingsuser, _params);
+            qpdenseaulsolver.qpdenseaulloaddefaults(n, state.qpdenseaulsettingsuser, _params);
+            state.qpbleicfirstcall = true;
+            state.dbgskipconstraintnormalization = false;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "MinQPSetLinearTerm: B contains infinite or NaN elements");
+            minqpsetlineartermfast(state, b, _params);
+        }
+
+
+        /*************************************************************************
+        This  function  sets  dense  quadratic  term  for  QP solver. By  default,
+        quadratic term is zero.
+
+        SUPPORT BY QP SOLVERS:
+
+        Dense quadratic term can be handled by following QP solvers:
+        * QuickQP
+        * BLEIC-QP
+        * Dense-AUL-QP
+
+        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 isn't used
+                        * if False, symmetric matrix  A  is  given  by  its lower
+                          triangle, and the upper triangle isn't 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,
+            alglib.xparams _params)
+        {
+            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, _params), "MinQPSetQuadraticTerm: A contains infinite or NaN elements");
+            minqpsetquadratictermfast(state, a, isupper, 0.0, _params);
+        }
+
+
+        /*************************************************************************
+        This  function  sets  sparse  quadratic  term  for  QP solver. By default,
+        quadratic  term  is  zero.  This  function  overrides  previous  calls  to
+        minqpsetquadraticterm() or minqpsetquadratictermsparse().
+
+        SUPPORT BY QP SOLVERS:
+
+        Sparse quadratic term can be handled by following QP solvers:
+        * QuickQP
+        * BLEIC-QP
+        * Dense-AUL-QP (internally converts sparse matrix to dense format)
+
+        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 isn't used
+                        * if False, symmetric matrix  A  is  given  by  its lower
+                          triangle, and the upper triangle isn't used
+                        * if not given, both lower and upper  triangles  must  be
+                          filled.
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetquadratictermsparse(minqpstate state,
+            sparse.sparsematrix a,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int t0 = 0;
+            int t1 = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            n = state.n;
+            alglib.ap.assert(sparse.sparsegetnrows(a, _params)==n, "MinQPSetQuadraticTermSparse: Rows(A)<>N");
+            alglib.ap.assert(sparse.sparsegetncols(a, _params)==n, "MinQPSetQuadraticTermSparse: Cols(A)<>N");
+            sparse.sparsecopytocrsbuf(a, state.sparsea, _params);
+            state.sparseaupper = isupper;
+            state.akind = 1;
+            
+            //
+            // Estimate norm of A
+            // (it will be used later in the quadratic penalty function)
+            //
+            state.absamax = 0;
+            state.absasum = 0;
+            state.absasum2 = 0;
+            t0 = 0;
+            t1 = 0;
+            while( sparse.sparseenumerate(a, ref t0, ref t1, ref i, ref j, ref v, _params) )
+            {
+                if( i==j )
+                {
+                    
+                    //
+                    // Diagonal terms are counted only once
+                    //
+                    state.absamax = Math.Max(state.absamax, v);
+                    state.absasum = state.absasum+v;
+                    state.absasum2 = state.absasum2+v*v;
+                }
+                if( (j>i && isupper) || (j<i && !isupper) )
+                {
+                    
+                    //
+                    // Offdiagonal terms are counted twice
+                    //
+                    state.absamax = Math.Max(state.absamax, v);
+                    state.absasum = state.absasum+2*v;
+                    state.absasum2 = state.absasum2+2*v*v;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "MinQPSetStartingPoint: X contains infinite or NaN elements");
+            minqpsetstartingpointfast(state, x, _params);
+        }
+
+
+        /*************************************************************************
+        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))
+            
+        Specification of non-zero origin affects function being minimized, but not
+        constraints. Box and  linear  constraints  are  still  calculated  without
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "MinQPSetOrigin: B contains infinite or NaN elements");
+            minqpsetoriginfast(state, xorigin, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets scaling coefficients.
+
+        ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+        size and gradient are scaled before comparison  with  tolerances)  and  as
+        preconditioner.
+
+        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
+
+        If you do not know how to choose scales of your variables, you can:
+        * read www.alglib.net/optimization/scaling.php article
+        * use minqpsetscaleautodiag(), which calculates scale  using  diagonal  of
+          the  quadratic  term:  S  is  set to 1/sqrt(diag(A)), which works well
+          sometimes.
+
+        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 minqpsetscale(minqpstate state,
+            double[] s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(s)>=state.n, "MinQPSetScale: Length(S)<N");
+            for(i=0; i<=state.n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(s[i]), "MinQPSetScale: S contains infinite or NAN elements");
+                alglib.ap.assert((double)(s[i])!=(double)(0), "MinQPSetScale: S contains zero elements");
+            }
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.s[i] = Math.Abs(s[i]);
+            }
+            state.stype = 0;
+        }
+
+
+        /*************************************************************************
+        This function sets automatic evaluation of variable scaling.
+
+        IMPORTANT: this function works only for  matrices  with positive  diagonal
+                   elements! Zero or negative elements will  result  in  -9  error
+                   code  being  returned.  Specify  scale  vector  manually   with
+                   minqpsetscale() in such cases.
+
+        ALGLIB optimizers use scaling matrices to test stopping  conditions  (step
+        size and gradient are scaled before comparison  with  tolerances)  and  as
+        preconditioner.
+
+        The  best  way  to  set  scaling  is  to manually specify variable scales.
+        However, sometimes you just need quick-and-dirty solution  -  either  when
+        you perform fast prototyping, or when you know your problem well  and  you
+        are 100% sure that this quick solution is robust enough in your case.
+
+        One such solution is to evaluate scale of I-th variable as 1/Sqrt(A[i,i]),
+        where A[i,i] is an I-th diagonal element of the quadratic term.
+
+        Such approach works well sometimes, but you have to be careful here.
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+
+          -- ALGLIB --
+             Copyright 26.12.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetscaleautodiag(minqpstate state,
+            alglib.xparams _params)
+        {
+            state.stype = 1;
+        }
+
+
+        /*************************************************************************
+        This function tells solver to use BLEIC-based algorithm and sets  stopping
+        criteria for the algorithm.
+
+        This algorithm is fast  enough  for large-scale  problems  with  following
+        properties:
+        a) feasible initial point, moderate amount of general linear constraints
+        b) arbitrary (can be infeasible) initial point, small  amount  of  general
+           linear constraints (say, hundred or less)
+
+        If you solve large-scale QP problem with many inequality  constraints  and
+        without initial feasibility guarantees, consider  using  DENSE-AUL  solver
+        instead. Initial feasibility detection stage by BLEIC may take too long on
+        such problems.
+
+        ALGORITHM FEATURES:
+
+        * supports dense and sparse QP problems
+        * supports box and general linear equality/inequality constraints
+        * can solve all types of problems  (convex,  semidefinite,  nonconvex)  as
+          long as they are bounded from below under constraints.
+          Say, it is possible to solve "min{-x^2} subject to -1<=x<=+1".
+          Of course, global  minimum  is found only  for  positive  definite   and
+          semidefinite  problems.  As  for indefinite ones - only local minimum is
+          found.
+
+        ALGORITHM OUTLINE:
+
+        * BLEIC-QP solver is just a driver function for MinBLEIC solver; it solves
+          quadratic  programming   problem   as   general   linearly   constrained
+          optimization problem, which is solved by means of BLEIC solver  (part of
+          ALGLIB, active set method).
+          
+        ALGORITHM LIMITATIONS:
+        * This algorithm is inefficient on  problems with hundreds  and  thousands
+          of general inequality constraints and infeasible initial point.  Initial
+          feasibility detection stage may take too long on such constraint sets.
+          Consider using DENSE-AUL instead.
+        * unlike QuickQP solver, this algorithm does not perform Newton steps  and
+          does not use Level 3 BLAS. Being general-purpose active set  method,  it
+          can activate constraints only one-by-one. Thus, its performance is lower
+          than that of QuickQP.
+        * its precision is also a bit  inferior  to  that  of   QuickQP.  BLEIC-QP
+          performs only LBFGS steps (no Newton steps), which are good at detecting
+          neighborhood of the solution, buy needs many iterations to find solution
+          with more than 6 digits of precision.
+
+        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 constrained gradient vector, v[i]=g[i]*s[i]
+                        * g - gradient
+                        * s - scaling coefficients set by MinQPSetScale()
+            EpsF    -   >=0
+                        The  subroutine  finishes its work if exploratory steepest
+                        descent  step  on  k+1-th iteration  satisfies   following
+                        condition:  |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1}
+            EpsX    -   >=0
+                        The  subroutine  finishes its work if exploratory steepest
+                        descent  step  on  k+1-th iteration  satisfies   following
+                        condition:  
+                        * |.| means Euclidian norm
+                        * v - scaled step vector, v[i]=dx[i]/s[i]
+                        * dx - step vector, dx=X(k+1)-X(k)
+                        * s - scaling coefficients set by MinQPSetScale()
+            MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                        iterations is unlimited. NOTE: this  algorithm uses  LBFGS
+                        iterations,  which  are  relatively  cheap,  but   improve
+                        function value only a bit. So you will need many iterations
+                        to converge - from 0.1*N to 10*N, depending  on  problem's
+                        condition number.
+
+        IT IS VERY IMPORTANT TO CALL MinQPSetScale() WHEN YOU USE THIS  ALGORITHM
+        BECAUSE ITS STOPPING CRITERIA ARE SCALE-DEPENDENT!
+
+        Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead
+        to automatic stopping criterion selection (presently it is  small    step
+        length, but it may change in the future versions of ALGLIB).
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetalgobleic(minqpstate state,
+            double epsg,
+            double epsf,
+            double epsx,
+            int maxits,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(epsg), "MinQPSetAlgoBLEIC: EpsG is not finite number");
+            alglib.ap.assert((double)(epsg)>=(double)(0), "MinQPSetAlgoBLEIC: negative EpsG");
+            alglib.ap.assert(math.isfinite(epsf), "MinQPSetAlgoBLEIC: EpsF is not finite number");
+            alglib.ap.assert((double)(epsf)>=(double)(0), "MinQPSetAlgoBLEIC: negative EpsF");
+            alglib.ap.assert(math.isfinite(epsx), "MinQPSetAlgoBLEIC: EpsX is not finite number");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "MinQPSetAlgoBLEIC: negative EpsX");
+            alglib.ap.assert(maxits>=0, "MinQPSetAlgoBLEIC: negative MaxIts!");
+            state.algokind = 2;
+            if( (((double)(epsg)==(double)(0) && (double)(epsf)==(double)(0)) && (double)(epsx)==(double)(0)) && maxits==0 )
+            {
+                epsx = 1.0E-6;
+            }
+            state.qpbleicsettingsuser.epsg = epsg;
+            state.qpbleicsettingsuser.epsf = epsf;
+            state.qpbleicsettingsuser.epsx = epsx;
+            state.qpbleicsettingsuser.maxits = maxits;
+        }
+
+
+        /*************************************************************************
+        This function tells QP solver to use Dense-AUL algorithm and sets stopping
+        criteria for the algorithm.
+
+        ALGORITHM FEATURES:
+
+        * supports  box  and  dense/sparse  general   linear   equality/inequality
+          constraints
+        * convergence is theoretically proved for positive-definite  (convex)   QP
+          problems. Semidefinite and non-convex problems can be solved as long  as
+          they  are   bounded  from  below  under  constraints,  although  without
+          theoretical guarantees.
+        * this solver is better than QP-BLEIC on problems  with  large  number  of
+          general linear constraints. It better handles infeasible initial points.
+
+        ALGORITHM OUTLINE:
+
+        * this  algorithm   is   an   augmented   Lagrangian   method  with  dense
+          preconditioner (hence  its  name).  It  is  similar  to  barrier/penalty
+          methods, but much more precise and faster.
+        * it performs several outer iterations in order to refine  values  of  the
+          Lagrange multipliers. Single outer  iteration  is  a  solution  of  some
+          unconstrained optimization problem: first  it  performs  dense  Cholesky
+          factorization of the Hessian in order to build preconditioner  (adaptive
+          regularization is applied to enforce positive  definiteness),  and  then
+          it uses L-BFGS optimizer to solve optimization problem.
+        * typically you need about 5-10 outer iterations to converge to solution
+          
+        ALGORITHM LIMITATIONS:
+
+        * because dense Cholesky driver is used, this algorithm has O(N^2)  memory
+          requirements and O(OuterIterations*N^3) minimum running time.  From  the
+          practical  point  of  view,  it  limits  its  applicability  by  several
+          thousands of variables.
+          From  the  other  side,  variables  count  is  the most limiting factor,
+          and dependence on constraint count is  much  more  lower. Assuming  that
+          constraint matrix is sparse, it may handle tens of thousands  of general
+          linear constraints.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            EpsX    -   >=0, stopping criteria for inner optimizer.
+                        Inner  iterations  are  stopped  when  step  length  (with
+                        variable scaling being applied) is less than EpsX.
+                        See  minqpsetscale()  for  more  information  on  variable
+                        scaling.
+            Rho     -   penalty coefficient, Rho>0:
+                        * large enough  that  algorithm  converges  with   desired
+                          precision.
+                        * not TOO large to prevent ill-conditioning
+                        * recommended values are 100, 1000 or 10000
+            ItsCnt  -   number of outer iterations:
+                        * recommended values: 10-15 (although  in  most  cases  it
+                          converges within 5 iterations, you may need a  few  more
+                          to be sure).
+                        * ItsCnt=0 means that small number of outer iterations  is
+                          automatically chosen (10 iterations in current version).
+                        * ItsCnt=1 means that AUL algorithm performs just as usual
+                          penalty method.
+                        * ItsCnt>1 means that  AUL  algorithm  performs  specified
+                          number of outer iterations
+
+        IT IS VERY IMPORTANT TO CALL minqpsetscale() WHEN YOU USE THIS  ALGORITHM
+        BECAUSE ITS CONVERGENCE PROPERTIES AND STOPPING CRITERIA ARE SCALE-DEPENDENT!
+
+        NOTE: Passing  EpsX=0  will  lead  to  automatic  step  length  selection
+              (specific step length chosen may change in the future  versions  of
+              ALGLIB, so it is better to specify step length explicitly).
+
+          -- ALGLIB --
+             Copyright 20.08.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetalgodenseaul(minqpstate state,
+            double epsx,
+            double rho,
+            int itscnt,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(epsx), "MinQPSetAlgoDenseAUL: EpsX is not finite number");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "MinQPSetAlgoDenseAUL: negative EpsX");
+            alglib.ap.assert(math.isfinite(rho), "MinQPSetAlgoDenseAUL: Rho is not finite number");
+            alglib.ap.assert((double)(rho)>(double)(0), "MinQPSetAlgoDenseAUL: non-positive Rho");
+            alglib.ap.assert(itscnt>=0, "MinQPSetAlgoDenseAUL: negative ItsCnt!");
+            state.algokind = 4;
+            if( (double)(epsx)==(double)(0) )
+            {
+                epsx = 1.0E-8;
+            }
+            if( itscnt==0 )
+            {
+                itscnt = 10;
+            }
+            state.qpdenseaulsettingsuser.epsx = epsx;
+            state.qpdenseaulsettingsuser.outerits = itscnt;
+            state.qpdenseaulsettingsuser.rho = rho;
+        }
+
+
+        /*************************************************************************
+        This function tells solver to use QuickQP  algorithm:  special  extra-fast
+        algorithm for problems with box-only constrants. It may  solve  non-convex
+        problems as long as they are bounded from below under constraints.
+
+        ALGORITHM FEATURES:
+        * many times (from 5x to 50x!) faster than BLEIC-based QP solver; utilizes
+          accelerated methods for activation of constraints.
+        * supports dense and sparse QP problems
+        * supports ONLY box constraints; general linear constraints are NOT
+          supported by this solver
+        * can solve all types of problems  (convex,  semidefinite,  nonconvex)  as
+          long as they are bounded from below under constraints.
+          Say, it is possible to solve "min{-x^2} subject to -1<=x<=+1".
+          In convex/semidefinite case global minimum  is  returned,  in  nonconvex
+          case - algorithm returns one of the local minimums.
+
+        ALGORITHM OUTLINE:
+
+        * algorithm  performs  two kinds of iterations: constrained CG  iterations
+          and constrained Newton iterations
+        * initially it performs small number of constrained CG  iterations,  which
+          can efficiently activate/deactivate multiple constraints
+        * after CG phase algorithm tries to calculate Cholesky  decomposition  and
+          to perform several constrained Newton steps. If  Cholesky  decomposition
+          failed (matrix is indefinite even under constraints),  we  perform  more
+          CG iterations until we converge to such set of constraints  that  system
+          matrix becomes  positive  definite.  Constrained  Newton  steps  greatly
+          increase convergence speed and precision.
+        * algorithm interleaves CG and Newton iterations which  allows  to  handle
+          indefinite matrices (CG phase) and quickly converge after final  set  of
+          constraints is found (Newton phase). Combination of CG and Newton phases
+          is called "outer iteration".
+        * it is possible to turn off Newton  phase  (beneficial  for  semidefinite
+          problems - Cholesky decomposition will fail too often)
+          
+        ALGORITHM LIMITATIONS:
+
+        * algorithm does not support general  linear  constraints;  only  box ones
+          are supported
+        * Cholesky decomposition for sparse problems  is  performed  with  Skyline
+          Cholesky solver, which is intended for low-profile matrices. No profile-
+          reducing reordering of variables is performed in this version of ALGLIB.
+        * problems with near-zero negative eigenvalues (or exacty zero  ones)  may
+          experience about 2-3x performance penalty. The reason is  that  Cholesky
+          decomposition can not be performed until we identify directions of  zero
+          and negative curvature and activate corresponding boundary constraints -
+          but we need a lot of trial and errors because these directions  are hard
+          to notice in the matrix spectrum.
+          In this case you may turn off Newton phase of algorithm.
+          Large negative eigenvalues  are  not  an  issue,  so  highly  non-convex
+          problems can be solved very efficiently.
+
+        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 constrained gradient vector, v[i]=g[i]*s[i]
+                        * g - gradient
+                        * s - scaling coefficients set by MinQPSetScale()
+            EpsF    -   >=0
+                        The  subroutine  finishes its work if exploratory steepest
+                        descent  step  on  k+1-th iteration  satisfies   following
+                        condition:  |F(k+1)-F(k)|<=EpsF*max{|F(k)|,|F(k+1)|,1}
+            EpsX    -   >=0
+                        The  subroutine  finishes its work if exploratory steepest
+                        descent  step  on  k+1-th iteration  satisfies   following
+                        condition:  
+                        * |.| means Euclidian norm
+                        * v - scaled step vector, v[i]=dx[i]/s[i]
+                        * dx - step vector, dx=X(k+1)-X(k)
+                        * s - scaling coefficients set by MinQPSetScale()
+            MaxOuterIts-maximum number of OUTER iterations.  One  outer  iteration
+                        includes some amount of CG iterations (from 5 to  ~N)  and
+                        one or several (usually small amount) Newton steps.  Thus,
+                        one outer iteration has high cost, but can greatly  reduce
+                        funcation value.
+                        Use 0 if you do not want to limit number of outer iterations.
+            UseNewton-  use Newton phase or not:
+                        * Newton phase improves performance of  positive  definite
+                          dense problems (about 2 times improvement can be observed)
+                        * can result in some performance penalty  on  semidefinite
+                          or slightly negative definite  problems  -  each  Newton
+                          phase will bring no improvement (Cholesky failure),  but
+                          still will require computational time.
+                        * if you doubt, you can turn off this  phase  -  optimizer
+                          will retain its most of its high speed.
+
+        IT IS VERY IMPORTANT TO CALL MinQPSetScale() WHEN YOU USE THIS  ALGORITHM
+        BECAUSE ITS STOPPING CRITERIA ARE SCALE-DEPENDENT!
+
+        Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead
+        to automatic stopping criterion selection (presently it is  small    step
+        length, but it may change in the future versions of ALGLIB).
+
+          -- ALGLIB --
+             Copyright 22.05.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetalgoquickqp(minqpstate state,
+            double epsg,
+            double epsf,
+            double epsx,
+            int maxouterits,
+            bool usenewton,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(epsg), "MinQPSetAlgoQuickQP: EpsG is not finite number");
+            alglib.ap.assert((double)(epsg)>=(double)(0), "MinQPSetAlgoQuickQP: negative EpsG");
+            alglib.ap.assert(math.isfinite(epsf), "MinQPSetAlgoQuickQP: EpsF is not finite number");
+            alglib.ap.assert((double)(epsf)>=(double)(0), "MinQPSetAlgoQuickQP: negative EpsF");
+            alglib.ap.assert(math.isfinite(epsx), "MinQPSetAlgoQuickQP: EpsX is not finite number");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "MinQPSetAlgoQuickQP: negative EpsX");
+            alglib.ap.assert(maxouterits>=0, "MinQPSetAlgoQuickQP: negative MaxOuterIts!");
+            state.algokind = 3;
+            if( (((double)(epsg)==(double)(0) && (double)(epsf)==(double)(0)) && (double)(epsx)==(double)(0)) && maxouterits==0 )
+            {
+                epsx = 1.0E-6;
+            }
+            state.qqpsettingsuser.maxouterits = maxouterits;
+            state.qqpsettingsuser.epsg = epsg;
+            state.qqpsettingsuser.epsf = epsf;
+            state.qqpsettingsuser.epsx = epsx;
+            state.qqpsettingsuser.cnphase = usenewton;
+        }
+
+
+        /*************************************************************************
+        This function sets box constraints for QP solver
+
+        Box constraints are inactive by default (after  initial  creation).  After
+        being  set,  they  are  preserved until explicitly turned off with another
+        SetBC() call.
+
+        All QP solvers may handle box constraints.
+
+        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,
+            alglib.xparams _params)
+        {
+            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 sets dense linear constraints for QP optimizer.
+
+        This  function  overrides  results  of  previous  calls  to  minqpsetlc(),
+        minqpsetlcsparse() and minqpsetlcmixed().  After  call  to  this  function
+        sparse constraints are dropped, and you have only those constraints  which
+        were specified in the present call.
+
+        If you want  to  specify  mixed  (with  dense  and  sparse  terms)  linear
+        constraints, you should call minqpsetlcmixed().
+
+        SUPPORT BY QP SOLVERS:
+
+        Following QP solvers can handle dense linear constraints:
+        * BLEIC-QP          -   handles them  with  high  precision,  but  may  be
+                                inefficient for problems with hundreds of constraints
+        * Dense-AUL-QP      -   handles them with moderate precision (approx. 10^-6),
+                                may efficiently handle thousands of constraints.
+
+        Following QP solvers can NOT handle dense linear constraints:
+        * QuickQP           -   can not handle general linear constraints
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with MinQPCreate 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 violation due  to  numerical  errors  and
+                algorithmic limitations (BLEIC-QP solver is most  precise,  AUL-QP
+                solver is less precise).
+
+          -- ALGLIB --
+             Copyright 19.06.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetlc(minqpstate state,
+            double[,] c,
+            int[] ct,
+            int k,
+            alglib.xparams _params)
+        {
+            sparse.sparsematrix dummyc = new sparse.sparsematrix();
+            int[] dummyct = new int[0];
+
+            minqpsetlcmixed(state, c, ct, k, dummyc, dummyct, 0, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets sparse linear constraints for QP optimizer.
+
+        This  function  overrides  results  of  previous  calls  to  minqpsetlc(),
+        minqpsetlcsparse() and minqpsetlcmixed().  After  call  to  this  function
+        dense constraints are dropped, and you have only those  constraints  which
+        were specified in the present call.
+
+        If you want  to  specify  mixed  (with  dense  and  sparse  terms)  linear
+        constraints, you should call minqpsetlcmixed().
+
+        SUPPORT BY QP SOLVERS:
+
+        Following QP solvers can handle sparse linear constraints:
+        * BLEIC-QP          -   handles them  with  high  precision,  but can  not
+                                utilize their sparsity - sparse constraint  matrix
+                                is silently converted to dense  format.  Thus,  it
+                                may be inefficient for problems with  hundreds  of
+                                constraints.
+        * Dense-AUL-QP      -   although this solver uses dense linear algebra  to
+                                calculate   Cholesky   preconditioner,   it    may
+                                efficiently  handle  sparse  constraints.  It  may
+                                solve problems  with  hundreds  and  thousands  of
+                                constraints. The only drawback is  that  precision
+                                of constraint handling is typically within 1E-4...
+                                ..1E-6 range.
+
+        Following QP solvers can NOT handle sparse linear constraints:
+        * QuickQP           -   can not handle general linear constraints
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with MinQPCreate call.
+            C       -   linear  constraints,  sparse  matrix  with  dimensions  at
+                        least [K,N+1]. If matrix has  larger  size,  only  leading
+                        Kx(N+1) rectangle is used.
+                        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
+
+        NOTE 1: linear (non-bound) constraints are satisfied only approximately  -
+                there always exists some violation due  to  numerical  errors  and
+                algorithmic limitations (BLEIC-QP solver is most  precise,  AUL-QP
+                solver is less precise).
+
+          -- ALGLIB --
+             Copyright 22.08.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetlcsparse(minqpstate state,
+            sparse.sparsematrix c,
+            int[] ct,
+            int k,
+            alglib.xparams _params)
+        {
+            double[,] dummyc = new double[0,0];
+            int[] dummyct = new int[0];
+
+            minqpsetlcmixed(state, dummyc, dummyct, 0, c, ct, k, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets mixed linear constraints, which include a set of  dense
+        rows, and a set of sparse rows.
+
+        This  function  overrides  results  of  previous  calls  to  minqpsetlc(),
+        minqpsetlcsparse() and minqpsetlcmixed().
+
+        This function may be useful if constraint matrix includes large number  of
+        both types of rows - dense and sparse. If you have just a few sparse rows,
+        you  may  represent  them  in  dense  format  without loosing performance.
+        Similarly, if you have just a few dense rows, you may store them in sparse
+        format with almost same performance.
+
+        SUPPORT BY QP SOLVERS:
+
+        Following QP solvers can handle mixed dense/sparse linear constraints:
+        * BLEIC-QP          -   handles them  with  high  precision,  but can  not
+                                utilize their sparsity - sparse constraint  matrix
+                                is silently converted to dense  format.  Thus,  it
+                                may be inefficient for problems with  hundreds  of
+                                constraints.
+        * Dense-AUL-QP      -   although this solver uses dense linear algebra  to
+                                calculate   Cholesky   preconditioner,   it    may
+                                efficiently  handle  sparse  constraints.  It  may
+                                solve problems  with  hundreds  and  thousands  of
+                                constraints. The only drawback is  that  precision
+                                of constraint handling is typically within 1E-4...
+                                ..1E-6 range.
+
+        Following QP solvers can NOT handle mixed linear constraints:
+        * QuickQP           -   can not handle general linear constraints at all
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with MinQPCreate call.
+            DenseC  -   dense linear constraints, array[K,N+1].
+                        Each row of DenseC 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 DenseC (including right part) must be finite.
+            DenseCT -   type of constraints, array[K]:
+                        * if DenseCT[i]>0, then I-th constraint is DenseC[i,*]*x >= DenseC[i,n+1]
+                        * if DenseCT[i]=0, then I-th constraint is DenseC[i,*]*x  = DenseC[i,n+1]
+                        * if DenseCT[i]<0, then I-th constraint is DenseC[i,*]*x <= DenseC[i,n+1]
+            DenseK  -   number of equality/inequality constraints, DenseK>=0
+            SparseC -   linear  constraints,  sparse  matrix  with  dimensions  at
+                        least [SparseK,N+1]. If matrix has  larger  size,  only  leading
+                        SPARSEKx(N+1) rectangle is used.
+                        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.
+            SparseCT-   type of sparse constraints, array[K]:
+                        * if SparseCT[i]>0, then I-th constraint is SparseC[i,*]*x >= SparseC[i,n+1]
+                        * if SparseCT[i]=0, then I-th constraint is SparseC[i,*]*x  = SparseC[i,n+1]
+                        * if SparseCT[i]<0, then I-th constraint is SparseC[i,*]*x <= SparseC[i,n+1]
+            SparseK -   number of sparse equality/inequality constraints, K>=0
+
+        NOTE 1: linear (non-bound) constraints are satisfied only approximately  -
+                there always exists some violation due  to  numerical  errors  and
+                algorithmic limitations (BLEIC-QP solver is most  precise,  AUL-QP
+                solver is less precise).
+
+          -- ALGLIB --
+             Copyright 22.08.2016 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpsetlcmixed(minqpstate state,
+            double[,] densec,
+            int[] densect,
+            int densek,
+            sparse.sparsematrix sparsec,
+            int[] sparsect,
+            int sparsek,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int j0 = 0;
+            double v = 0;
+            int[] srcidx = new int[0];
+            int[] dstidx = new int[0];
+            double[] s = new double[0];
+            int[] rs = new int[0];
+            int[] eoffs = new int[0];
+            int[] roffs = new int[0];
+            double[] v2 = new double[0];
+            int[] eidx = new int[0];
+            double[] eval = new double[0];
+            int t0 = 0;
+            int t1 = 0;
+            int nnz = 0;
+            int i_ = 0;
+
+            n = state.n;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(densek>=0, "MinQPSetLCMixed: K<0");
+            alglib.ap.assert(densek==0 || alglib.ap.cols(densec)>=n+1, "MinQPSetLCMixed: Cols(C)<N+1");
+            alglib.ap.assert(alglib.ap.rows(densec)>=densek, "MinQPSetLCMixed: Rows(DenseC)<DenseK");
+            alglib.ap.assert(alglib.ap.len(densect)>=densek, "MinQPSetLCMixed: Length(DenseCT)<DenseK");
+            alglib.ap.assert(apserv.apservisfinitematrix(densec, densek, n+1, _params), "MinQPSetLCMixed: C contains infinite or NaN values!");
+            alglib.ap.assert(sparsek>=0, "MinQPSetLCMixed: SparseK<0");
+            alglib.ap.assert(sparsek==0 || sparse.sparsegetncols(sparsec, _params)>=n+1, "MinQPSetLCMixed: Cols(SparseC)<N+1");
+            alglib.ap.assert(sparsek==0 || sparse.sparsegetnrows(sparsec, _params)>=sparsek, "MinQPSetLCMixed: Rows(SparseC)<SparseK");
+            alglib.ap.assert(alglib.ap.len(sparsect)>=sparsek, "MinQPSetLCMixed: Length(SparseCT)<SparseK");
+            
+            //
+            // Init.
+            // Handle zero number of constraints
+            //
+            state.nec = 0;
+            state.nic = 0;
+            state.snec = 0;
+            state.snic = 0;
+            if( densek+sparsek==0 )
+            {
+                return;
+            }
+            
+            //
+            // If we have dense constraints
+            //
+            if( densek>0 )
+            {
+                
+                //
+                // Equality constraints are stored first, in the upper
+                // NEC rows of State.CLEIC matrix. Inequality constraints
+                // are stored in the next NIC rows.
+                //
+                // NOTE: we convert inequality constraints to the form
+                // A*x<=b before copying them.
+                //
+                apserv.rmatrixsetlengthatleast(ref state.cleic, densek, n+1, _params);
+                for(i=0; i<=densek-1; i++)
+                {
+                    if( densect[i]==0 )
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec,i_] = densec[i,i_];
+                        }
+                        state.nec = state.nec+1;
+                    }
+                }
+                for(i=0; i<=densek-1; i++)
+                {
+                    if( densect[i]!=0 )
+                    {
+                        if( densect[i]>0 )
+                        {
+                            for(i_=0; i_<=n;i_++)
+                            {
+                                state.cleic[state.nec+state.nic,i_] = -densec[i,i_];
+                            }
+                        }
+                        else
+                        {
+                            for(i_=0; i_<=n;i_++)
+                            {
+                                state.cleic[state.nec+state.nic,i_] = densec[i,i_];
+                            }
+                        }
+                        state.nic = state.nic+1;
+                    }
+                }
+                
+                //
+                // Normalize rows of State.CLEIC: each row must have unit norm.
+                // Norm is calculated using first N elements (i.e. right part is
+                // not counted when we calculate norm).
+                //
+                if( !state.dbgskipconstraintnormalization )
+                {
+                    for(i=0; i<=densek-1; i++)
+                    {
+                        v = 0;
+                        for(j=0; j<=n-1; j++)
+                        {
+                            v = v+math.sqr(state.cleic[i,j]);
+                        }
+                        if( (double)(v)==(double)(0) )
+                        {
+                            continue;
+                        }
+                        v = 1/Math.Sqrt(v);
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[i,i_] = v*state.cleic[i,i_];
+                        }
+                    }
+                }
+            }
+            
+            //
+            // If we have sparse constraints
+            //
+            // NOTE: we have to write our own conversion code
+            //       because standard implementation converts whole
+            //       matrix, and we need just leading rectangle.
+            //
+            if( sparsek>0 )
+            {
+                
+                //
+                // Calculate metrics:
+                // * row sizes for CRS storage.
+                // * row norms for scaling
+                // * number of non-zero elements
+                // * in addition, we check for finiteness of matrix elements
+                //
+                rs = new int[sparsek];
+                v2 = new double[sparsek];
+                for(i=0; i<=sparsek-1; i++)
+                {
+                    rs[i] = 0;
+                    v2[i] = 0;
+                }
+                t0 = 0;
+                t1 = 0;
+                nnz = 0;
+                while( sparse.sparseenumerate(sparsec, ref t0, ref t1, ref i, ref j, ref v, _params) )
+                {
+                    if( i>sparsek-1 || j>n )
+                    {
+                        continue;
+                    }
+                    alglib.ap.assert(math.isfinite(v), "MinQPSetLCSparse: C contains infinite or NAN values");
+                    nnz = nnz+1;
+                    rs[i] = rs[i]+1;
+                    if( j<n )
+                    {
+                        v2[i] = v2[i]+v*v;
+                    }
+                }
+                
+                //
+                // If we have at least one non-zero element
+                //
+                if( nnz>0 )
+                {
+                    
+                    //
+                    // Determine actual order in which constraints are stored.
+                    //
+                    // Equality constraints are stored first, in the upper
+                    // SNEC rows of State.CLEIC matrix. Inequality constraints
+                    // are stored in the next NIC rows.
+                    //
+                    // NOTE: we convert inequality constraints to the form
+                    // A*x<=b before copying them.
+                    //
+                    srcidx = new int[sparsek];
+                    dstidx = new int[sparsek];
+                    s = new double[sparsek];
+                    for(i=0; i<=sparsek-1; i++)
+                    {
+                        srcidx[i] = -1;
+                    }
+                    for(i=0; i<=sparsek-1; i++)
+                    {
+                        if( sparsect[i]==0 )
+                        {
+                            s[i] = 1;
+                            srcidx[state.snec] = i;
+                            state.snec = state.snec+1;
+                        }
+                        else
+                        {
+                            if( sparsect[i]>0 )
+                            {
+                                s[i] = -1;
+                            }
+                            else
+                            {
+                                s[i] = 1;
+                            }
+                            srcidx[sparsek-1-state.snic] = i;
+                            state.snic = state.snic+1;
+                        }
+                    }
+                    for(i=0; i<=sparsek-1; i++)
+                    {
+                        alglib.ap.assert(srcidx[i]>=0, "MinQPSetLCSparse: integrity check failed");
+                    }
+                    for(i=0; i<=sparsek-1; i++)
+                    {
+                        dstidx[srcidx[i]] = i;
+                    }
+                    
+                    //
+                    // Prepare CRS conversion.
+                    //
+                    state.scleic.m = sparsek;
+                    state.scleic.n = n+1;
+                    apserv.ivectorsetlengthatleast(ref state.scleic.ridx, sparsek+1, _params);
+                    eoffs = new int[sparsek+1];
+                    state.scleic.ridx[0] = 0;
+                    eoffs[0] = 0;
+                    for(i=1; i<=sparsek; i++)
+                    {
+                        state.scleic.ridx[i] = state.scleic.ridx[i-1]+rs[srcidx[i-1]];
+                        eoffs[i] = state.scleic.ridx[i];
+                    }
+                    for(i=0; i<=sparsek-1; i++)
+                    {
+                        if( (double)(v2[i])>(double)(0) && !state.dbgskipconstraintnormalization )
+                        {
+                            v2[i] = s[i]/Math.Sqrt(v2[i]);
+                        }
+                        else
+                        {
+                            v2[i] = s[i];
+                        }
+                    }
+                    apserv.ivectorsetlengthatleast(ref state.scleic.idx, nnz, _params);
+                    apserv.rvectorsetlengthatleast(ref state.scleic.vals, nnz, _params);
+                    t0 = 0;
+                    t1 = 0;
+                    while( sparse.sparseenumerate(sparsec, ref t0, ref t1, ref i, ref j, ref v, _params) )
+                    {
+                        if( i>sparsek-1 || j>n )
+                        {
+                            continue;
+                        }
+                        j0 = eoffs[dstidx[i]];
+                        state.scleic.idx[j0] = j;
+                        state.scleic.vals[j0] = v*v2[i];
+                        eoffs[dstidx[i]] = j0+1;
+                    }
+                    for(i=0; i<=sparsek-1; i++)
+                    {
+                        alglib.ap.assert(eoffs[i]==state.scleic.ridx[i+1], "MinQPSetLCSparse: integrity check failed");
+                    }
+                    sparse.sparsecreatecrsinplace(state.scleic, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function solves quadratic programming problem.
+
+        Prior to calling this function you should choose solver by means of one of
+        the following functions:
+
+        * minqpsetalgoquickqp()     - for QuickQP solver
+        * minqpsetalgobleic()       - for BLEIC-QP solver
+        * minqpsetalgodenseaul()    - for Dense-AUL-QP solver
+
+        These functions also allow you to control stopping criteria of the solver.
+        If you did not set solver,  MinQP  subpackage  will  automatically  select
+        solver for your problem and will run it with default stopping criteria.
+
+        However, it is better to set explicitly solver and its stopping criteria.
+
+        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.
+             Special thanks to Elvira Illarionova  for  important  suggestions  on
+             the linearly constrained QP algorithm.
+        *************************************************************************/
+        public static void minqpoptimize(minqpstate state,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int idst = 0;
+            int isrc = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int nbc = 0;
+            int i_ = 0;
+
+            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;
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.havebndl[i] )
+                {
+                    nbc = nbc+1;
+                }
+                if( state.havebndu[i] )
+                {
+                    nbc = nbc+1;
+                }
+            }
+            
+            //
+            // Initial point:
+            // * if we have starting point in StartX, we just have to bound it
+            // * if we do not have StartX, deduce initial point from boundary constraints
+            //
+            if( state.havex )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    state.xs[i] = state.startx[i];
+                    if( state.havebndl[i] && (double)(state.xs[i])<(double)(state.bndl[i]) )
+                    {
+                        state.xs[i] = state.bndl[i];
+                    }
+                    if( state.havebndu[i] && (double)(state.xs[i])>(double)(state.bndu[i]) )
+                    {
+                        state.xs[i] = state.bndu[i];
+                    }
+                }
+            }
+            else
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    if( state.havebndl[i] && state.havebndu[i] )
+                    {
+                        state.xs[i] = 0.5*(state.bndl[i]+state.bndu[i]);
+                        continue;
+                    }
+                    if( state.havebndl[i] )
+                    {
+                        state.xs[i] = state.bndl[i];
+                        continue;
+                    }
+                    if( state.havebndu[i] )
+                    {
+                        state.xs[i] = state.bndu[i];
+                        continue;
+                    }
+                    state.xs[i] = 0;
+                }
+            }
+            
+            //
+            // Effective scale
+            //
+            apserv.rvectorsetlengthatleast(ref state.effectives, n, _params);
+            if( state.stype==0 )
+            {
+                
+                //
+                // User scale (or default one)
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    state.effectives[i] = state.s[i];
+                }
+            }
+            else
+            {
+                if( state.stype==1 )
+                {
+                    
+                    //
+                    // Diagonal is used for scaling:
+                    // * unpack
+                    // * convert to scale, return error on failure
+                    //
+                    if( state.akind==0 )
+                    {
+                        
+                        //
+                        // Unpack CQM structure
+                        //
+                        cqmodels.cqmgetdiaga(state.a, ref state.effectives, _params);
+                    }
+                    else
+                    {
+                        if( state.akind==1 )
+                        {
+                            for(i=0; i<=n-1; i++)
+                            {
+                                state.effectives[i] = sparse.sparseget(state.sparsea, i, i, _params);
+                            }
+                        }
+                        else
+                        {
+                            alglib.ap.assert(false, "MinQPOptimize: integrity check failed");
+                        }
+                    }
+                    for(i=0; i<=n-1; i++)
+                    {
+                        if( (double)(state.effectives[i])<=(double)(0) )
+                        {
+                            state.repterminationtype = -9;
+                            return;
+                        }
+                        state.effectives[i] = 1/Math.Sqrt(state.effectives[i]);
+                    }
+                }
+                else
+                {
+                    alglib.ap.assert(false, "MinQPOptimize: integrity check failed");
+                }
+            }
+            
+            //
+            // QP-BLEIC solver
+            //
+            if( state.algokind==2 )
+            {
+                
+                //
+                // Combine dense and sparse constraints in temporary dense storage
+                //
+                apserv.rmatrixsetlengthatleast(ref state.ecleic, state.nec+state.nic+state.snec+state.snic, n+1, _params);
+                for(i=0; i<=state.nec-1; i++)
+                {
+                    for(i_=0; i_<=n;i_++)
+                    {
+                        state.ecleic[i,i_] = state.cleic[i,i_];
+                    }
+                }
+                for(i=0; i<=state.snec-1; i++)
+                {
+                    alglib.ap.assert(state.scleic.matrixtype==1, "MinQPOptimize: integrity check failed");
+                    idst = i+state.nec;
+                    for(j=0; j<=n; j++)
+                    {
+                        state.ecleic[idst,j] = 0;
+                    }
+                    j0 = state.scleic.ridx[i];
+                    j1 = state.scleic.ridx[i+1];
+                    for(j=j0; j<=j1-1; j++)
+                    {
+                        state.ecleic[idst,state.scleic.idx[j]] = state.scleic.vals[j];
+                    }
+                }
+                for(i=0; i<=state.nic-1; i++)
+                {
+                    idst = i+state.nec+state.snec;
+                    isrc = i+state.nec;
+                    for(i_=0; i_<=n;i_++)
+                    {
+                        state.ecleic[idst,i_] = state.cleic[isrc,i_];
+                    }
+                }
+                for(i=0; i<=state.snic-1; i++)
+                {
+                    alglib.ap.assert(state.scleic.matrixtype==1, "MinQPOptimize: integrity check failed");
+                    idst = i+state.nec+state.snec+state.nic;
+                    isrc = i+state.snec;
+                    for(j=0; j<=n; j++)
+                    {
+                        state.ecleic[idst,j] = 0;
+                    }
+                    j0 = state.scleic.ridx[isrc];
+                    j1 = state.scleic.ridx[isrc+1];
+                    for(j=j0; j<=j1-1; j++)
+                    {
+                        state.ecleic[idst,state.scleic.idx[j]] = state.scleic.vals[j];
+                    }
+                }
+                
+                //
+                // Solve
+                //
+                qpbleicsolver.qpbleicoptimize(state.a, state.sparsea, state.akind, state.sparseaupper, state.absasum, state.absasum2, state.b, state.bndl, state.bndu, state.effectives, state.xorigin, n, state.ecleic, state.nec+state.snec, state.nic+state.snic, state.qpbleicsettingsuser, state.qpbleicbuf, ref state.qpbleicfirstcall, ref state.xs, ref state.repterminationtype, _params);
+                state.repinneriterationscount = state.qpbleicbuf.repinneriterationscount;
+                state.repouteriterationscount = state.qpbleicbuf.repouteriterationscount;
+                return;
+            }
+            
+            //
+            // QuickQP solver
+            //
+            if( state.algokind==3 )
+            {
+                if( state.nec+state.nic>0 )
+                {
+                    state.repterminationtype = -5;
+                    return;
+                }
+                if( state.snec+state.snic>0 )
+                {
+                    state.repterminationtype = -5;
+                    return;
+                }
+                qqpsolver.qqpoptimize(state.a, state.sparsea, state.dummyr2, state.akind, state.sparseaupper, state.b, state.bndl, state.bndu, state.effectives, state.xorigin, n, state.qqpsettingsuser, state.qqpbuf, state.xs, ref state.repterminationtype, _params);
+                state.repinneriterationscount = state.qqpbuf.repinneriterationscount;
+                state.repouteriterationscount = state.qqpbuf.repouteriterationscount;
+                state.repncholesky = state.qqpbuf.repncholesky;
+                return;
+            }
+            
+            //
+            // QP-DenseAUL solver
+            //
+            if( state.algokind==4 )
+            {
+                
+                //
+                // Solve
+                //
+                qpdenseaulsolver.qpdenseauloptimize(state.a, state.sparsea, state.akind, state.sparseaupper, state.b, state.bndl, state.bndu, state.effectives, state.xorigin, n, state.cleic, state.nec, state.nic, state.scleic, state.snec, state.snic, !state.dbgskipconstraintnormalization, state.qpdenseaulsettingsuser, state.qpdenseaulbuf, ref state.xs, ref state.repterminationtype, _params);
+                state.repinneriterationscount = state.qpdenseaulbuf.repinneriterationscount;
+                state.repouteriterationscount = state.qpdenseaulbuf.repouteriterationscount;
+                state.repncholesky = state.qpdenseaulbuf.repncholesky;
+                return;
+            }
+            
+            //
+            // Integrity check failed - unknown solver
+            //
+            alglib.ap.assert(false, "MinQPOptimize: integrity check failed - unknown solver");
+        }
+
+
+        /*************************************************************************
+        QP solver results
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[0..N-1], solution.
+                        This array is allocated and initialized only when
+                        Rep.TerminationType parameter is positive (success).
+            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.
+                        
+                        Failure codes returned by algorithm are:
+                        * -9    failure of the automatic scale evaluation:  one of
+                                the diagonal elements of  the  quadratic  term  is
+                                non-positive.  Specify variable scales manually!
+                        * -5    inappropriate solver was used:
+                                * QuickQP solver for problem with  general  linear
+                                  constraints
+                        * -4    BLEIC-QP/QuickQP   solver    found   unconstrained
+                                direction  of   negative  curvature  (function  is
+                                unbounded from below even under constraints),   no
+                                meaningful minimum can be found.
+                        * -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.
+                                
+                        Completion codes specific for Cholesky algorithm:
+                        *  4   successful completion
+                        
+                        Completion codes specific for BLEIC/QuickQP algorithms:
+                        *  1   relative function improvement is no more than EpsF.
+                        *  2   scaled step is no more than EpsX.
+                        *  4   scaled gradient norm is no more than EpsG.
+                        *  5   MaxIts steps was taken
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minqpresults(minqpstate state,
+            ref double[] x,
+            minqpreport rep,
+            alglib.xparams _params)
+        {
+            x = new double[0];
+
+            minqpresultsbuf(state, ref x, rep, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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.xs[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,
+            alglib.xparams _params)
+        {
+            int i_ = 0;
+
+            for(i_=0; i_<=state.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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+            double v = 0;
+            int j0 = 0;
+            int j1 = 0;
+
+            n = state.n;
+            state.akind = 0;
+            cqmodels.cqmseta(state.a, a, isupper, 1.0, _params);
+            if( (double)(s)>(double)(0) )
+            {
+                apserv.rvectorsetlengthatleast(ref state.tmp0, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    state.tmp0[i] = a[i,i]+s;
+                }
+                cqmodels.cqmrewritedensediagonal(state.a, state.tmp0, _params);
+            }
+            
+            //
+            // Estimate norm of A
+            // (it will be used later in the quadratic penalty function)
+            //
+            state.absamax = 0;
+            state.absasum = 0;
+            state.absasum2 = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( isupper )
+                {
+                    j0 = i;
+                    j1 = n-1;
+                }
+                else
+                {
+                    j0 = 0;
+                    j1 = i;
+                }
+                for(j=j0; j<=j1; j++)
+                {
+                    v = Math.Abs(a[i,j]);
+                    state.absamax = Math.Max(state.absamax, v);
+                    state.absasum = state.absasum+v;
+                    state.absasum2 = state.absasum2+v*v;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal function 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,
+            alglib.xparams _params)
+        {
+            cqmodels.cqmrewritedensediagonal(state.a, s, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i_ = 0;
+
+            n = state.n;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xorigin[i_] = xorigin[i_];
+            }
+        }
+
+
+    }
+    public class reviseddualsimplex
+    {
+        /*************************************************************************
+        This object stores settings for dual simplex solver
+        *************************************************************************/
+        public class dualsimplexsettings : apobject
+        {
+            public double pivottol;
+            public double perturbmag;
+            public int maxtrfage;
+            public int trftype;
+            public int ratiotest;
+            public int pricing;
+            public int shifting;
+            public dualsimplexsettings()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                dualsimplexsettings _result = new dualsimplexsettings();
+                _result.pivottol = pivottol;
+                _result.perturbmag = perturbmag;
+                _result.maxtrfage = maxtrfage;
+                _result.trftype = trftype;
+                _result.ratiotest = ratiotest;
+                _result.pricing = pricing;
+                _result.shifting = shifting;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This object stores basis and its triangular factorization for DualSimplexState
+        *************************************************************************/
+        public class dualsimplexbasis : apobject
+        {
+            public int ns;
+            public int m;
+            public int[] idx;
+            public int[] nidx;
+            public bool[] isbasic;
+            public int trftype;
+            public bool isvalidtrf;
+            public int trfage;
+            public double[,] denselu;
+            public sparse.sparsematrix sparsel;
+            public sparse.sparsematrix sparseu;
+            public sparse.sparsematrix sparseut;
+            public int[] rowpermbwd;
+            public int[] colpermbwd;
+            public double[] densepfieta;
+            public double[] densemu;
+            public int[] rk;
+            public int[] dk;
+            public double[] dseweights;
+            public bool dsevalid;
+            public double eminu;
+            public double[] wtmp0;
+            public double[] wtmp1;
+            public double[] wtmp2;
+            public int[] nrs;
+            public int[] tcinvidx;
+            public double[,] denselu2;
+            public int[] densep2;
+            public int[] densep2c;
+            public sparse.sparsematrix sparselu1;
+            public sparse.sparsematrix sparselu2;
+            public sptrf.sluv2buffer lubuf2;
+            public int[] tmpi;
+            public double[] utmp0;
+            public int[] utmpi;
+            public sparse.sparsematrix sparseludbg;
+            public dualsimplexbasis()
+            {
+                init();
+            }
+            public override void init()
+            {
+                idx = new int[0];
+                nidx = new int[0];
+                isbasic = new bool[0];
+                denselu = new double[0,0];
+                sparsel = new sparse.sparsematrix();
+                sparseu = new sparse.sparsematrix();
+                sparseut = new sparse.sparsematrix();
+                rowpermbwd = new int[0];
+                colpermbwd = new int[0];
+                densepfieta = new double[0];
+                densemu = new double[0];
+                rk = new int[0];
+                dk = new int[0];
+                dseweights = new double[0];
+                wtmp0 = new double[0];
+                wtmp1 = new double[0];
+                wtmp2 = new double[0];
+                nrs = new int[0];
+                tcinvidx = new int[0];
+                denselu2 = new double[0,0];
+                densep2 = new int[0];
+                densep2c = new int[0];
+                sparselu1 = new sparse.sparsematrix();
+                sparselu2 = new sparse.sparsematrix();
+                lubuf2 = new sptrf.sluv2buffer();
+                tmpi = new int[0];
+                utmp0 = new double[0];
+                utmpi = new int[0];
+                sparseludbg = new sparse.sparsematrix();
+            }
+            public override alglib.apobject make_copy()
+            {
+                dualsimplexbasis _result = new dualsimplexbasis();
+                _result.ns = ns;
+                _result.m = m;
+                _result.idx = (int[])idx.Clone();
+                _result.nidx = (int[])nidx.Clone();
+                _result.isbasic = (bool[])isbasic.Clone();
+                _result.trftype = trftype;
+                _result.isvalidtrf = isvalidtrf;
+                _result.trfage = trfage;
+                _result.denselu = (double[,])denselu.Clone();
+                _result.sparsel = (sparse.sparsematrix)sparsel.make_copy();
+                _result.sparseu = (sparse.sparsematrix)sparseu.make_copy();
+                _result.sparseut = (sparse.sparsematrix)sparseut.make_copy();
+                _result.rowpermbwd = (int[])rowpermbwd.Clone();
+                _result.colpermbwd = (int[])colpermbwd.Clone();
+                _result.densepfieta = (double[])densepfieta.Clone();
+                _result.densemu = (double[])densemu.Clone();
+                _result.rk = (int[])rk.Clone();
+                _result.dk = (int[])dk.Clone();
+                _result.dseweights = (double[])dseweights.Clone();
+                _result.dsevalid = dsevalid;
+                _result.eminu = eminu;
+                _result.wtmp0 = (double[])wtmp0.Clone();
+                _result.wtmp1 = (double[])wtmp1.Clone();
+                _result.wtmp2 = (double[])wtmp2.Clone();
+                _result.nrs = (int[])nrs.Clone();
+                _result.tcinvidx = (int[])tcinvidx.Clone();
+                _result.denselu2 = (double[,])denselu2.Clone();
+                _result.densep2 = (int[])densep2.Clone();
+                _result.densep2c = (int[])densep2c.Clone();
+                _result.sparselu1 = (sparse.sparsematrix)sparselu1.make_copy();
+                _result.sparselu2 = (sparse.sparsematrix)sparselu2.make_copy();
+                _result.lubuf2 = (sptrf.sluv2buffer)lubuf2.make_copy();
+                _result.tmpi = (int[])tmpi.Clone();
+                _result.utmp0 = (double[])utmp0.Clone();
+                _result.utmpi = (int[])utmpi.Clone();
+                _result.sparseludbg = (sparse.sparsematrix)sparseludbg.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This object stores subproblem for DualSimplexState object
+        *************************************************************************/
+        public class dualsimplexsubproblem : apobject
+        {
+            public int ns;
+            public int m;
+            public double[] rawc;
+            public double[] bndl;
+            public double[] bndu;
+            public int[] bndt;
+            public double[] xa;
+            public double[] d;
+            public int state;
+            public double[] xb;
+            public double[] bndlb;
+            public double[] bndub;
+            public int[] bndtb;
+            public double[] effc;
+            public double[] colscales;
+            public dualsimplexsubproblem()
+            {
+                init();
+            }
+            public override void init()
+            {
+                rawc = new double[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                bndt = new int[0];
+                xa = new double[0];
+                d = new double[0];
+                xb = new double[0];
+                bndlb = new double[0];
+                bndub = new double[0];
+                bndtb = new int[0];
+                effc = new double[0];
+                colscales = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                dualsimplexsubproblem _result = new dualsimplexsubproblem();
+                _result.ns = ns;
+                _result.m = m;
+                _result.rawc = (double[])rawc.Clone();
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.bndt = (int[])bndt.Clone();
+                _result.xa = (double[])xa.Clone();
+                _result.d = (double[])d.Clone();
+                _result.state = state;
+                _result.xb = (double[])xb.Clone();
+                _result.bndlb = (double[])bndlb.Clone();
+                _result.bndub = (double[])bndub.Clone();
+                _result.bndtb = (int[])bndtb.Clone();
+                _result.effc = (double[])effc.Clone();
+                _result.colscales = (double[])colscales.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This object stores state of the DSS solver.
+        MUST be initialized with DSSInit().
+        *************************************************************************/
+        public class dualsimplexstate : apobject
+        {
+            public double[] varscales;
+            public double[] rowscales;
+            public double[] rawbndl;
+            public double[] rawbndu;
+            public int ns;
+            public int m;
+            public sparse.sparsematrix a;
+            public sparse.sparsematrix at;
+            public dualsimplexbasis basis;
+            public dualsimplexsubproblem primary;
+            public dualsimplexsubproblem phase1;
+            public dualsimplexsubproblem phase3;
+            public double[] repx;
+            public double[] repy;
+            public double[] repdx;
+            public int[] repstats;
+            public double repf;
+            public double repprimalerror;
+            public double repdualerror;
+            public int repterminationtype;
+            public int repiterationscount;
+            public int repiterationscount1;
+            public int repiterationscount2;
+            public int repiterationscount3;
+            public int[] possibleflips;
+            public int possibleflipscnt;
+            public double[] dfctmp0;
+            public double[] dfctmp1;
+            public double[] dfctmp2;
+            public int[] ustmpi;
+            public double[] tmp0;
+            public double[] tmp1;
+            public double[] tmp2;
+            public double[] alphar;
+            public double[] rhor;
+            public double[] tau;
+            public double[] alphaq;
+            public double[] alphaqim;
+            public int[] eligibleset;
+            public int[] harrisset;
+            public dualsimplexstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                varscales = new double[0];
+                rowscales = new double[0];
+                rawbndl = new double[0];
+                rawbndu = new double[0];
+                a = new sparse.sparsematrix();
+                at = new sparse.sparsematrix();
+                basis = new dualsimplexbasis();
+                primary = new dualsimplexsubproblem();
+                phase1 = new dualsimplexsubproblem();
+                phase3 = new dualsimplexsubproblem();
+                repx = new double[0];
+                repy = new double[0];
+                repdx = new double[0];
+                repstats = new int[0];
+                possibleflips = new int[0];
+                dfctmp0 = new double[0];
+                dfctmp1 = new double[0];
+                dfctmp2 = new double[0];
+                ustmpi = new int[0];
+                tmp0 = new double[0];
+                tmp1 = new double[0];
+                tmp2 = new double[0];
+                alphar = new double[0];
+                rhor = new double[0];
+                tau = new double[0];
+                alphaq = new double[0];
+                alphaqim = new double[0];
+                eligibleset = new int[0];
+                harrisset = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                dualsimplexstate _result = new dualsimplexstate();
+                _result.varscales = (double[])varscales.Clone();
+                _result.rowscales = (double[])rowscales.Clone();
+                _result.rawbndl = (double[])rawbndl.Clone();
+                _result.rawbndu = (double[])rawbndu.Clone();
+                _result.ns = ns;
+                _result.m = m;
+                _result.a = (sparse.sparsematrix)a.make_copy();
+                _result.at = (sparse.sparsematrix)at.make_copy();
+                _result.basis = (dualsimplexbasis)basis.make_copy();
+                _result.primary = (dualsimplexsubproblem)primary.make_copy();
+                _result.phase1 = (dualsimplexsubproblem)phase1.make_copy();
+                _result.phase3 = (dualsimplexsubproblem)phase3.make_copy();
+                _result.repx = (double[])repx.Clone();
+                _result.repy = (double[])repy.Clone();
+                _result.repdx = (double[])repdx.Clone();
+                _result.repstats = (int[])repstats.Clone();
+                _result.repf = repf;
+                _result.repprimalerror = repprimalerror;
+                _result.repdualerror = repdualerror;
+                _result.repterminationtype = repterminationtype;
+                _result.repiterationscount = repiterationscount;
+                _result.repiterationscount1 = repiterationscount1;
+                _result.repiterationscount2 = repiterationscount2;
+                _result.repiterationscount3 = repiterationscount3;
+                _result.possibleflips = (int[])possibleflips.Clone();
+                _result.possibleflipscnt = possibleflipscnt;
+                _result.dfctmp0 = (double[])dfctmp0.Clone();
+                _result.dfctmp1 = (double[])dfctmp1.Clone();
+                _result.dfctmp2 = (double[])dfctmp2.Clone();
+                _result.ustmpi = (int[])ustmpi.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmp1 = (double[])tmp1.Clone();
+                _result.tmp2 = (double[])tmp2.Clone();
+                _result.alphar = (double[])alphar.Clone();
+                _result.rhor = (double[])rhor.Clone();
+                _result.tau = (double[])tau.Clone();
+                _result.alphaq = (double[])alphaq.Clone();
+                _result.alphaqim = (double[])alphaqim.Clone();
+                _result.eligibleset = (int[])eligibleset.Clone();
+                _result.harrisset = (int[])harrisset.Clone();
+                return _result;
+            }
+        };
+
+
+
+
+        public const int maxforcedrestarts = 1;
+        public const int safetrfage = 5;
+        public const int defaultmaxtrfage = 50;
+        public const double minbeta = 1.0E-4;
+        public const double maxudecay = 0.001;
+        public const double shiftlen = 1.0E-12;
+        public const double dtol = 1.0E-6;
+        public const double xtol = 1.0E-9;
+        public const double alphatrigger = 1.0E8*math.machineepsilon;
+        public const double alphatrigger2 = 0.001;
+        public const int ssinvalid = 0;
+        public const int ssvalidxn = 1;
+        public const int ssvalid = 2;
+        public const int ccfixed = 0;
+        public const int cclower = 1;
+        public const int ccupper = 2;
+        public const int ccrange = 3;
+        public const int ccfree = 4;
+        public const int ccinfeasible = 5;
+
+
+        public static void dsssettingsinit(dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            settings.pivottol = 10*Math.Sqrt(math.machineepsilon);
+            settings.perturbmag = 10*settings.pivottol;
+            settings.maxtrfage = defaultmaxtrfage;
+            settings.trftype = 3;
+            settings.ratiotest = 1;
+            settings.pricing = 1;
+            settings.shifting = 2;
+        }
+
+
+        /*************************************************************************
+        This function initializes DSS structure. Previously  allocated  memory  is
+        reused as much as possible.
+
+        Default state of the problem is zero cost vector, all variables are  fixed
+        at zero.
+
+          -- ALGLIB --
+             Copyright 01.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dssinit(int n,
+            dualsimplexstate s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(n>0, "DSSInit: N<=0");
+            s.ns = n;
+            s.m = 0;
+            apserv.rvectorsetlengthatleast(ref s.varscales, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.rawbndl, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.rawbndu, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                s.varscales[i] = 1.0;
+                s.rawbndl[i] = 0.0;
+                s.rawbndu[i] = 0.0;
+            }
+            subprobleminit(n, s.primary, _params);
+            basisinit(n, 0, s.basis, _params);
+            apserv.rvectorsetlengthatleast(ref s.repx, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.repdx, 0, _params);
+            apserv.ivectorsetlengthatleast(ref s.repstats, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                s.repx[i] = 0.0;
+                s.repstats[i] = 1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function specifies LP problem
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with minlpcreate() call.
+            
+            BndL    -   lower bounds, array[N].
+            BndU    -   upper bounds, array[N].
+            SV      -   scales, array[N].
+            
+            DenseA  -   dense array[K,N], dense linear constraints
+            SparseA -   sparse linear constraints, sparsematrix[K,N] in CRS format
+            AKind   -   type of A: 0 for dense, 1 for sparse
+            AL, AU  -   lower and upper bounds, array[K]
+            K       -   number of equality/inequality constraints, K>=0.
+            
+            ProposedBasis- basis to import from (if BasisType=2)
+            BasisInitType-  what to do with basis:
+                        * 0 - set new basis to all-logicals
+                        * 1 - try to reuse previous basis as much as possible
+                        * 2 - try to import basis from ProposedBasis
+            Settings-   algorithm settings
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dsssetproblem(dualsimplexstate state,
+            double[] c,
+            double[] bndl,
+            double[] bndu,
+            double[] sv,
+            double[,] densea,
+            sparse.sparsematrix sparsea,
+            int akind,
+            double[] al,
+            double[] au,
+            int k,
+            dualsimplexbasis proposedbasis,
+            int basisinittype,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int jj = 0;
+            int offs = 0;
+            int ns = 0;
+            int j0 = 0;
+            int j1 = 0;
+            bool processed = new bool();
+            int oldm = 0;
+            bool basisinitialized = new bool();
+            double v = 0;
+            double vv = 0;
+
+            ns = state.primary.ns;
+            oldm = state.primary.m;
+            
+            //
+            // Integrity checks
+            //
+            alglib.ap.assert(alglib.ap.len(bndl)>=ns, "DSSSetProblem: Length(BndL)<N");
+            alglib.ap.assert(alglib.ap.len(bndu)>=ns, "DSSSetProblem: Length(BndU)<N");
+            alglib.ap.assert(alglib.ap.len(sv)>=ns, "DSSSetProblem: Length(SV)<N");
+            alglib.ap.assert(apserv.isfinitevector(sv, ns, _params), "DSSSetProblem: SV contains infinite or NaN elements");
+            alglib.ap.assert(alglib.ap.len(c)>=ns, "SubproblemSetCost: Length(C)<N");
+            alglib.ap.assert(apserv.isfinitevector(c, ns, _params), "SubproblemSetCost: C contains infinite or NaN elements");
+            alglib.ap.assert(akind==0 || akind==1, "DSSSetProblem: incorrect AKind");
+            alglib.ap.assert((basisinittype==0 || basisinittype==1) || basisinittype==2, "DSSSetProblem: incorrect BasisInitType");
+            alglib.ap.assert(k>=0, "DSSSetProblem: K<0");
+            if( k>0 && akind==1 )
+            {
+                alglib.ap.assert(sparsea.m==k, "DSSSetProblem: rows(A)<>K");
+                alglib.ap.assert(sparsea.n==ns, "DSSSetProblem: cols(A)<>N");
+            }
+            
+            //
+            // Downgrade state
+            //
+            downgradestate(state.primary, ssinvalid, _params);
+            
+            //
+            // Reallocate storage
+            //
+            apserv.rvectorgrowto(ref state.primary.bndl, ns+k, _params);
+            apserv.rvectorgrowto(ref state.primary.bndu, ns+k, _params);
+            apserv.ivectorgrowto(ref state.primary.bndt, ns+k, _params);
+            apserv.rvectorgrowto(ref state.primary.rawc, ns+k, _params);
+            apserv.rvectorgrowto(ref state.primary.effc, ns+k, _params);
+            apserv.rvectorgrowto(ref state.primary.xa, ns+k, _params);
+            apserv.rvectorgrowto(ref state.primary.d, ns+k, _params);
+            apserv.rvectorgrowto(ref state.primary.xb, k, _params);
+            apserv.rvectorgrowto(ref state.primary.bndlb, k, _params);
+            apserv.rvectorgrowto(ref state.primary.bndub, k, _params);
+            apserv.ivectorgrowto(ref state.primary.bndtb, k, _params);
+            
+            //
+            // Save original problem formulation
+            //
+            state.ns = ns;
+            state.m = k;
+            for(i=0; i<=ns-1; i++)
+            {
+                state.varscales[i] = sv[i];
+                state.rawbndl[i] = bndl[i];
+                state.rawbndu[i] = bndu[i];
+            }
+            
+            //
+            // Setup cost, scale and box constraints
+            //
+            for(i=0; i<=ns-1; i++)
+            {
+                alglib.ap.assert(sv[i]>0, "DSSSetProblem: SV<=0");
+                state.primary.rawc[i] = c[i]*sv[i];
+                state.primary.effc[i] = c[i]*sv[i];
+            }
+            for(i=0; i<=ns-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(bndl[i]) || Double.IsNegativeInfinity(bndl[i]), "DSSSetProblem: BndL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(bndu[i]) || Double.IsPositiveInfinity(bndu[i]), "DSSSetProblem: BndU contains NAN or -INF");
+                state.primary.bndl[i] = bndl[i]/sv[i];
+                state.primary.bndu[i] = bndu[i]/sv[i];
+                
+                //
+                // Set bound type
+                //
+                if( math.isfinite(bndl[i]) && math.isfinite(bndu[i]) )
+                {
+                    if( (double)(bndl[i])>(double)(bndu[i]) )
+                    {
+                        state.primary.bndt[i] = ccinfeasible;
+                    }
+                    if( (double)(bndl[i])<(double)(bndu[i]) )
+                    {
+                        state.primary.bndt[i] = ccrange;
+                    }
+                    if( (double)(bndl[i])==(double)(bndu[i]) )
+                    {
+                        state.primary.bndt[i] = ccfixed;
+                    }
+                    continue;
+                }
+                if( math.isfinite(bndl[i]) && !math.isfinite(bndu[i]) )
+                {
+                    state.primary.bndt[i] = cclower;
+                    continue;
+                }
+                if( !math.isfinite(bndl[i]) && math.isfinite(bndu[i]) )
+                {
+                    state.primary.bndt[i] = ccupper;
+                    continue;
+                }
+                alglib.ap.assert(Double.IsNegativeInfinity(bndl[i]) && Double.IsPositiveInfinity(bndu[i]), "DSSSetProblem: integrity check failed");
+                state.primary.bndt[i] = ccfree;
+            }
+            
+            //
+            // Quick exit if no linear constraints is present
+            //
+            if( k==0 )
+            {
+                state.primary.m = 0;
+                basisinit(state.primary.ns, state.primary.m, state.basis, _params);
+                return;
+            }
+            
+            //
+            // Extend A with structural terms and transpose it:
+            // * allocate place for A^T extended with logical part.
+            // * copy with transposition
+            // * perform integrity check for array sizes
+            // * manually append new items
+            // * update DIdx/UIdx
+            //
+            processed = false;
+            state.primary.m = k;
+            if( akind==0 )
+            {
+                alglib.ap.assert(false, "DSSSetProblem: does not support dense inputs yet");
+            }
+            if( akind==1 )
+            {
+                
+                //
+                // Transpose constraints matrix, apply column and row scaling.
+                // Extend it with identity submatrix.
+                //
+                // NOTE: in order to improve stability of LU factorization we
+                //       normalize rows using 2-norm, not INF-norm. Having rows
+                //       normalized with 2-norm makes every element less than
+                //       1.0 in magnitude, which allows us later to move logical
+                //       columns to the beginning of LU factors without loosing
+                //       stability.
+                //
+                apserv.rvectorsetlengthatleast(ref state.at.vals, sparsea.ridx[k]+k, _params);
+                apserv.ivectorsetlengthatleast(ref state.at.idx, sparsea.ridx[k]+k, _params);
+                apserv.ivectorsetlengthatleast(ref state.at.ridx, ns+k+1, _params);
+                apserv.ivectorsetlengthatleast(ref state.at.didx, ns+k, _params);
+                apserv.ivectorsetlengthatleast(ref state.at.uidx, ns+k, _params);
+                sparse.sparsecopytransposecrsbuf(sparsea, state.at, _params);
+                apserv.rvectorsetlengthatleast(ref state.rowscales, k, _params);
+                for(i=0; i<=k-1; i++)
+                {
+                    state.rowscales[i] = 0;
+                }
+                for(i=0; i<=ns-1; i++)
+                {
+                    j0 = state.at.ridx[i];
+                    j1 = state.at.ridx[i+1]-1;
+                    v = sv[i];
+                    for(j=j0; j<=j1; j++)
+                    {
+                        vv = v*state.at.vals[j];
+                        jj = state.at.idx[j];
+                        state.at.vals[j] = vv;
+                        state.rowscales[jj] = state.rowscales[jj]+vv*vv;
+                    }
+                }
+                apserv.rvectorsetlengthatleast(ref state.tmp0, k, _params);
+                for(i=0; i<=k-1; i++)
+                {
+                    state.rowscales[i] = apserv.coalesce(Math.Sqrt(state.rowscales[i]), 1, _params);
+                    state.tmp0[i] = 1/state.rowscales[i];
+                }
+                for(i=0; i<=ns-1; i++)
+                {
+                    j0 = state.at.ridx[i];
+                    j1 = state.at.ridx[i+1]-1;
+                    for(j=j0; j<=j1; j++)
+                    {
+                        state.at.vals[j] = state.at.vals[j]*state.tmp0[state.at.idx[j]];
+                    }
+                }
+                alglib.ap.assert(alglib.ap.len(state.at.vals)>=sparsea.ridx[k]+k, "DSSSetProblem: integrity check failed");
+                alglib.ap.assert(alglib.ap.len(state.at.idx)>=sparsea.ridx[k]+k, "DSSSetProblem: integrity check failed");
+                alglib.ap.assert(alglib.ap.len(state.at.ridx)>=ns+k+1, "DSSSetProblem: integrity check failed");
+                alglib.ap.assert(alglib.ap.len(state.at.didx)>=ns+k, "DSSSetProblem: integrity check failed");
+                alglib.ap.assert(alglib.ap.len(state.at.uidx)>=ns+k, "DSSSetProblem: integrity check failed");
+                offs = state.at.ridx[ns];
+                for(i=0; i<=k-1; i++)
+                {
+                    state.at.vals[offs+i] = -1.0;
+                    state.at.idx[offs+i] = i;
+                    state.at.ridx[ns+i+1] = state.at.ridx[ns+i]+1;
+                    state.at.ninitialized = state.at.ninitialized+1;
+                }
+                state.at.m = state.at.m+k;
+                sparse.sparseinitduidx(state.at, _params);
+                processed = true;
+            }
+            alglib.ap.assert(processed, "DSSSetProblem: integrity check failed (akind)");
+            
+            //
+            // Copy AL, AU to BndL/BndT
+            //
+            for(i=0; i<=k-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(al[i]) || Double.IsNegativeInfinity(al[i]), "DSSSetProblem: AL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(au[i]) || Double.IsPositiveInfinity(au[i]), "DSSSetProblem: AU contains NAN or -INF");
+                state.primary.bndl[ns+i] = al[i]/state.rowscales[i];
+                state.primary.bndu[ns+i] = au[i]/state.rowscales[i];
+                
+                //
+                // Set bound type
+                //
+                if( math.isfinite(al[i]) && math.isfinite(au[i]) )
+                {
+                    if( (double)(al[i])>(double)(au[i]) )
+                    {
+                        state.primary.bndt[ns+i] = ccinfeasible;
+                    }
+                    if( (double)(al[i])<(double)(au[i]) )
+                    {
+                        state.primary.bndt[ns+i] = ccrange;
+                    }
+                    if( (double)(al[i])==(double)(au[i]) )
+                    {
+                        state.primary.bndt[ns+i] = ccfixed;
+                    }
+                    continue;
+                }
+                if( math.isfinite(al[i]) && !math.isfinite(au[i]) )
+                {
+                    state.primary.bndt[ns+i] = cclower;
+                    continue;
+                }
+                if( !math.isfinite(al[i]) && math.isfinite(au[i]) )
+                {
+                    state.primary.bndt[ns+i] = ccupper;
+                    continue;
+                }
+                alglib.ap.assert(Double.IsNegativeInfinity(al[i]) && Double.IsPositiveInfinity(au[i]), "DSSSetProblem: integrity check faoled");
+                state.primary.bndt[ns+i] = ccfree;
+            }
+            
+            //
+            // Depending on BasisInitType either start from all-logical basis
+            // or try to reuse already existing basis.
+            //
+            // NOTE: current version does not support basis shrinkage, only
+            //       growing basis can be reused.
+            //
+            basisinitialized = false;
+            if( basisinittype==2 )
+            {
+                
+                //
+                // Import basis from one proposed by caller
+                //
+                alglib.ap.assert(proposedbasis.ns==state.primary.ns, "DSSSetProblemX: unable to import basis, sizes do not match");
+                alglib.ap.assert(proposedbasis.m==state.primary.m, "DSSSetProblemX: unable to import basis, sizes do not match");
+                basisinitialized = basistryimportfrom(state.basis, proposedbasis, state.at, settings, _params);
+            }
+            if( basisinittype==1 && state.primary.m>=oldm )
+            {
+                
+                //
+                // New rows were added, try to reuse previous basis
+                //
+                for(i=oldm; i<=state.primary.m-1; i++)
+                {
+                    state.primary.rawc[ns+i] = 0.0;
+                    state.primary.effc[ns+i] = 0.0;
+                    state.primary.xa[ns+i] = 0.0;
+                    state.primary.d[ns+i] = 0.0;
+                }
+                basisinitialized = basistryresize(state.basis, state.primary.m, state.at, settings, _params);
+            }
+            if( !basisinitialized )
+            {
+                
+                //
+                // Straightforward code for all-logicals basis
+                //
+                for(i=0; i<=k-1; i++)
+                {
+                    state.primary.rawc[ns+i] = 0.0;
+                    state.primary.effc[ns+i] = 0.0;
+                    state.primary.xa[ns+i] = 0.0;
+                    state.primary.d[ns+i] = 0.0;
+                }
+                basisinit(state.primary.ns, state.primary.m, state.basis, _params);
+                basisfreshtrf(state.basis, state.at, settings, _params);
+            }
+            apserv.rvectorgrowto(ref state.repy, state.primary.m, _params);
+            apserv.rvectorgrowto(ref state.repdx, state.primary.m, _params);
+            apserv.ivectorgrowto(ref state.repstats, state.primary.ns+state.primary.m, _params);
+        }
+
+
+        /*************************************************************************
+        This function exports basis from the primary (phase II) subproblem.
+
+        INPUT PARAMETERS:
+            State   -   structure
+            
+        OUTPUT PARAMETERS
+            Basis   -   current basis exported (no factorization, only set of
+                        basis/nonbasic variables)
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dssexportbasis(dualsimplexstate state,
+            dualsimplexbasis basis,
+            alglib.xparams _params)
+        {
+            basisexportto(state.basis, basis, _params);
+        }
+
+
+        /*************************************************************************
+        This function performs actual solution.
+
+        INPUT PARAMETERS:
+            State   -   state
+            
+        Solution results can be found in fields  of  State  which  are  explicitly
+        declared as accessible by external code.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void dssoptimize(dualsimplexstate state,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            int nx = 0;
+            int m = 0;
+            int i = 0;
+            int j = 0;
+            int j0 = 0;
+            int j1 = 0;
+            double v = 0;
+            hqrnd.hqrndstate rs = new hqrnd.hqrndstate();
+
+            nx = state.primary.ns+state.primary.m;
+            m = state.primary.m;
+            
+            //
+            // Init report fields
+            //
+            state.repf = 0;
+            state.repiterationscount = 0;
+            state.repiterationscount1 = 0;
+            state.repiterationscount2 = 0;
+            state.repiterationscount3 = 0;
+            state.repterminationtype = 1;
+            state.repprimalerror = 0;
+            state.repdualerror = 0;
+            
+            //
+            // Handle case when M=0; after this block we assume that M>0.
+            //
+            if( m==0 )
+            {
+                solveboxonly(state, _params);
+                state.repf = 0;
+                for(i=0; i<=state.primary.ns-1; i++)
+                {
+                    state.repf = state.repf+state.repx[i]*state.primary.rawc[i];
+                }
+                unscaleandenforce(state.repx, state.repy, state.repdx, state, _params);
+                return;
+            }
+            
+            //
+            // Most basic check for correctness of box constraints
+            //
+            for(i=0; i<=nx-1; i++)
+            {
+                if( state.primary.bndt[i]==ccinfeasible )
+                {
+                    state.repterminationtype = -3;
+                    setzeroxystats(state, _params);
+                    return;
+                }
+            }
+            
+            //
+            // Initialization:
+            // * column scales, initial perturbed C[]
+            //
+            hqrnd.hqrndseed(7456, 2355, rs, _params);
+            apserv.rvectorsetlengthatleast(ref state.primary.colscales, nx, _params);
+            for(i=0; i<=nx-1; i++)
+            {
+                
+                //
+                // compute column scales
+                //
+                v = Math.Abs(state.primary.rawc[i]);
+                j0 = state.at.ridx[i];
+                j1 = state.at.ridx[i+1]-1;
+                for(j=j0; j<=j1; j++)
+                {
+                    v = Math.Max(v, Math.Abs(state.at.vals[j]));
+                }
+                state.primary.colscales[i] = apserv.coalesce(v, 1.0, _params);
+                
+                //
+                // apply perturbation
+                //
+                if( !isfree(state.primary, i, _params) )
+                {
+                    v = settings.perturbmag*state.primary.colscales[i]*(1+hqrnd.hqrnduniformr(rs, _params));
+                    if( hasbndu(state.primary, i, _params) )
+                    {
+                        v = -v;
+                    }
+                    state.primary.effc[i] = state.primary.rawc[i]+v;
+                }
+            }
+            
+            //
+            // Solve phase 1 subproblem, then perturbed subproblem
+            //
+            basisfreshtrf(state.basis, state.at, settings, _params);
+            if( state.primary.state==ssinvalid )
+            {
+                subprobleminferinitialxn(state, state.primary, _params);
+            }
+            if( state.primary.state==ssvalidxn )
+            {
+                subproblemhandlexnupdate(state, state.primary, _params);
+            }
+            alglib.ap.assert(state.primary.state==ssvalid, "DSS: integrity check failed (init)");
+            invokephase1(state, settings, _params);
+            if( state.repterminationtype<=0 )
+            {
+                
+                //
+                // Primal unbounded, dual infeasible
+                //
+                alglib.ap.assert(state.repterminationtype==-4, "DSS: integrity check for InvokePhase1() result failed");
+                state.repf = 0;
+                state.repprimalerror = 0;
+                state.repdualerror = 0;
+                setzeroxystats(state, _params);
+                return;
+            }
+            solvesubproblemdual(state, state.primary, false, settings, ref state.repterminationtype, _params);
+            if( state.repterminationtype<=0 )
+            {
+                
+                //
+                // Primal infeasible
+                //
+                alglib.ap.assert(state.repterminationtype==-3, "DSS: integrity check for SolveSubproblemDual() result failed");
+                state.repf = 0;
+                state.repprimalerror = 0;
+                state.repdualerror = 0;
+                setzeroxystats(state, _params);
+                return;
+            }
+            
+            //
+            // Remove perturbation from the cost vector,
+            // then use primal simplex to enforce dual feasibility
+            // after removal of the perturbation (if necessary).
+            //
+            subprobleminitphase3(state.primary, state.phase3, _params);
+            for(i=0; i<=nx-1; i++)
+            {
+                state.phase3.effc[i] = state.primary.rawc[i];
+            }
+            alglib.ap.assert(state.phase3.state>=ssvalidxn, "DSS: integrity check failed (remove perturbation)");
+            subproblemhandlexnupdate(state, state.phase3, _params);
+            solvesubproblemprimal(state, state.phase3, settings, ref state.repterminationtype, _params);
+            if( state.repterminationtype<=0 )
+            {
+                
+                //
+                // Dual infeasible, primal unbounded
+                //
+                alglib.ap.assert(state.repterminationtype==-4, "DSS: integrity check for SolveSubproblemPrimal() result failed");
+                state.repf = 0;
+                state.repprimalerror = 0;
+                state.repdualerror = 0;
+                setzeroxystats(state, _params);
+                return;
+            }
+            for(i=0; i<=nx-1; i++)
+            {
+                state.primary.xa[i] = state.phase3.xa[i];
+                if( hasbndl(state.primary, i, _params) )
+                {
+                    state.primary.xa[i] = Math.Max(state.primary.xa[i], state.primary.bndl[i]);
+                }
+                if( hasbndu(state.primary, i, _params) )
+                {
+                    state.primary.xa[i] = Math.Min(state.primary.xa[i], state.primary.bndu[i]);
+                }
+            }
+            
+            //
+            // Primal and dual feasible, problem solved
+            //
+            apserv.rvectorsetlengthatleast(ref state.tmp0, m, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmp1, m, _params);
+            state.repf = 0;
+            for(i=0; i<=state.primary.ns-1; i++)
+            {
+                state.repf = state.repf+state.primary.xa[i]*state.primary.rawc[i];
+            }
+            state.repprimalerror = 0;
+            state.repdualerror = 0;
+            state.repterminationtype = 1;
+            for(i=0; i<=m-1; i++)
+            {
+                state.tmp0[i] = state.primary.rawc[state.basis.idx[i]];
+            }
+            basissolvet(state.basis, state.tmp0, ref state.tmp1, ref state.tmp2, _params);
+            computeantv(state, state.tmp1, ref state.primary.d, _params);
+            for(i=0; i<=state.ns-1; i++)
+            {
+                j = state.basis.nidx[i];
+                state.primary.d[j] = state.primary.rawc[j]-state.primary.d[j];
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                state.repy[i] = state.tmp1[i];
+                state.repdx[i] = state.primary.d[state.ns+i];
+            }
+            for(i=0; i<=nx-1; i++)
+            {
+                if( state.basis.isbasic[i] )
+                {
+                    state.repstats[i] = 0;
+                    continue;
+                }
+                if( hasbndl(state.primary, i, _params) && (double)(state.primary.xa[i])==(double)(state.primary.bndl[i]) )
+                {
+                    state.repstats[i] = -1;
+                    continue;
+                }
+                if( hasbndu(state.primary, i, _params) && (double)(state.primary.xa[i])==(double)(state.primary.bndu[i]) )
+                {
+                    state.repstats[i] = 1;
+                    continue;
+                }
+                alglib.ap.assert(!hasbndl(state.primary, i, _params) && !hasbndu(state.primary, i, _params), "DSSOptimize: integrity check failed (zetta5)");
+                state.repstats[i] = 0;
+            }
+            for(i=0; i<=state.primary.ns-1; i++)
+            {
+                state.repx[i] = state.primary.xa[i];
+            }
+            unscaleandenforce(state.repx, state.repy, state.repdx, state, _params);
+        }
+
+
+        /*************************************************************************
+        This function initializes subproblem structure. Previously allocated memory
+        is reused as much as possible.
+
+        Default state of the problem is zero cost vector, all variables are  fixed
+        at zero, linear constraint matrix is zero.
+
+          -- ALGLIB --
+             Copyright 01.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void subprobleminit(int n,
+            dualsimplexsubproblem s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(n>0, "SubproblemInit: N<=0");
+            s.ns = n;
+            s.m = 0;
+            s.state = ssinvalid;
+            apserv.rvectorsetlengthatleast(ref s.xa, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.xb, 0, _params);
+            apserv.rvectorsetlengthatleast(ref s.d, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.rawc, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.effc, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.bndl, n, _params);
+            apserv.rvectorsetlengthatleast(ref s.bndu, n, _params);
+            apserv.ivectorsetlengthatleast(ref s.bndt, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                s.rawc[i] = 0;
+                s.effc[i] = 0;
+                s.bndl[i] = 0;
+                s.bndu[i] = 0;
+                s.bndt[i] = ccfixed;
+                s.xa[i] = 0.0;
+                s.d[i] = 0.0;
+            }
+        }
+
+
+        /*************************************************************************
+        This function initializes phase #1 subproblem which minimizes sum of  dual
+        infeasibilities.  It is required that total count of  non-boxed  non-fixed
+        variables is at least M.
+
+        It splits out basic components of XA[] to XB[]
+
+          -- ALGLIB --
+             Copyright 01.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void subprobleminitphase1(dualsimplexsubproblem s0,
+            dualsimplexbasis basis,
+            dualsimplexsubproblem s1,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            s1.ns = s0.ns;
+            s1.m = s0.m;
+            apserv.copyrealarray(s0.rawc, ref s1.rawc, _params);
+            apserv.copyrealarray(s0.effc, ref s1.effc, _params);
+            apserv.copyrealarray(s0.colscales, ref s1.colscales, _params);
+            apserv.copyrealarray(s0.bndl, ref s1.bndl, _params);
+            apserv.copyrealarray(s0.bndu, ref s1.bndu, _params);
+            apserv.copyintegerarray(s0.bndt, ref s1.bndt, _params);
+            apserv.copyrealarray(s0.xa, ref s1.xa, _params);
+            apserv.copyrealarray(s0.xb, ref s1.xb, _params);
+            apserv.copyrealarray(s0.bndlb, ref s1.bndlb, _params);
+            apserv.copyrealarray(s0.bndub, ref s1.bndub, _params);
+            apserv.copyintegerarray(s0.bndtb, ref s1.bndtb, _params);
+            apserv.copyrealarray(s0.d, ref s1.d, _params);
+            for(i=0; i<=s1.ns+s1.m-1; i++)
+            {
+                if( s1.bndt[i]==cclower )
+                {
+                    s1.bndt[i] = ccrange;
+                    s1.bndl[i] = 0;
+                    s1.bndu[i] = 1;
+                    s1.xa[i] = 0;
+                    continue;
+                }
+                if( s1.bndt[i]==ccupper )
+                {
+                    s1.bndt[i] = ccrange;
+                    s1.bndl[i] = -1;
+                    s1.bndu[i] = 0;
+                    s1.xa[i] = 0;
+                    continue;
+                }
+                if( s1.bndt[i]==ccfree )
+                {
+                    s1.bndt[i] = ccrange;
+                    s1.bndl[i] = -1;
+                    s1.bndu[i] = 1;
+                    if( (double)(s1.effc[i])>=(double)(0) )
+                    {
+                        s1.xa[i] = -1;
+                    }
+                    else
+                    {
+                        s1.xa[i] = 1;
+                    }
+                    continue;
+                }
+                s1.bndt[i] = ccfixed;
+                s1.bndl[i] = 0;
+                s1.bndu[i] = 0;
+                s1.xa[i] = 0;
+            }
+            s1.state = ssvalidxn;
+        }
+
+
+        /*************************************************************************
+        This function initializes phase #3 subproblem which applies primal simplex
+        method to the result of the phase #2.
+
+        It also performs modification of the subproblem in order to ensure that
+        initial point is primal feasible.
+
+        NOTE: this function expects that all components (basic and nonbasic ones)
+              are stored in XA[]
+
+          -- ALGLIB --
+             Copyright 01.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void subprobleminitphase3(dualsimplexsubproblem s0,
+            dualsimplexsubproblem s1,
+            alglib.xparams _params)
+        {
+            s1.ns = s0.ns;
+            s1.m = s0.m;
+            apserv.copyrealarray(s0.rawc, ref s1.rawc, _params);
+            apserv.copyrealarray(s0.effc, ref s1.effc, _params);
+            apserv.copyrealarray(s0.colscales, ref s1.colscales, _params);
+            apserv.copyrealarray(s0.bndl, ref s1.bndl, _params);
+            apserv.copyrealarray(s0.bndu, ref s1.bndu, _params);
+            apserv.copyintegerarray(s0.bndt, ref s1.bndt, _params);
+            apserv.copyrealarray(s0.xa, ref s1.xa, _params);
+            apserv.copyrealarray(s0.xb, ref s1.xb, _params);
+            apserv.copyrealarray(s0.bndlb, ref s1.bndlb, _params);
+            apserv.copyrealarray(s0.bndub, ref s1.bndub, _params);
+            apserv.copyintegerarray(s0.bndtb, ref s1.bndtb, _params);
+            apserv.copyrealarray(s0.d, ref s1.d, _params);
+            s1.state = ssvalidxn;
+        }
+
+
+        /*************************************************************************
+        This function infers nonbasic variables of X using sign of effective C[].
+
+        Only non-basic components of XN are changed; everything else is NOT updated.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void subprobleminferinitialxn(dualsimplexstate state,
+            dualsimplexsubproblem s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int ii = 0;
+            int bndt = 0;
+
+            for(ii=0; ii<=s.ns-1; ii++)
+            {
+                i = state.basis.nidx[ii];
+                bndt = s.bndt[i];
+                if( bndt==ccfixed || bndt==ccrange )
+                {
+                    if( s.effc[i]>=0 )
+                    {
+                        s.xa[i] = s.bndl[i];
+                    }
+                    else
+                    {
+                        s.xa[i] = s.bndu[i];
+                    }
+                    continue;
+                }
+                if( bndt==cclower )
+                {
+                    s.xa[i] = s.bndl[i];
+                    continue;
+                }
+                if( bndt==ccupper )
+                {
+                    s.xa[i] = s.bndu[i];
+                    continue;
+                }
+                if( bndt==ccfree )
+                {
+                    s.xa[i] = 0.0;
+                    continue;
+                }
+                alglib.ap.assert(false, "SubproblemInferInitialXN: integrity check failed (infeasible constraint)");
+            }
+            s.state = ssvalidxn;
+        }
+
+
+        /*************************************************************************
+        This function infers basic variables of X using values of non-basic vars
+        and updates reduced cost vector D and target function Z. Sets state age
+        to zero.
+
+        D[] is allocated during computations.
+
+        Temporary vectors Tmp0 and Tmp1 are used (reallocated as needed).
+
+        NOTE: this function expects that both nonbasic and basic components are
+              stored in XA[]. XB[] array is not referenced.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void subproblemhandlexnupdate(dualsimplexstate state,
+            dualsimplexsubproblem s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int m = 0;
+            int nn = 0;
+
+            alglib.ap.assert(s.state>=ssvalidxn, "SubproblemHandleXNUpdate: integrity check failed (XN is not valid)");
+            nn = s.ns;
+            m = s.m;
+            
+            //
+            // Compute nonbasic components
+            //
+            computeanxn(state, s, s.xa, ref state.tmp0, _params);
+            basissolve(state.basis, state.tmp0, ref state.tmp1, ref state.tmp2, _params);
+            for(i=0; i<=m-1; i++)
+            {
+                s.xa[state.basis.idx[i]] = -state.tmp1[i];
+            }
+            
+            //
+            // Compute D
+            //
+            for(i=0; i<=m-1; i++)
+            {
+                state.tmp0[i] = s.effc[state.basis.idx[i]];
+            }
+            basissolvet(state.basis, state.tmp0, ref state.tmp1, ref state.tmp2, _params);
+            computeantv(state, state.tmp1, ref s.d, _params);
+            for(i=0; i<=nn-1; i++)
+            {
+                j = state.basis.nidx[i];
+                s.d[j] = s.effc[j]-s.d[j];
+            }
+            
+            //
+            // Update state validity/age
+            //
+            s.state = ssvalid;
+        }
+
+
+        /*************************************************************************
+        This function performs initial dual feasibility correction on the subproblem.
+        It assumes that problem state is at least ssValidXN. After call to this
+        function the problem state is set to ssValid.
+
+        This function returns dual feasibility error after dual feasibility correction.
+
+        NOTE: this function expects that both nonbasic and basic components are
+              stored in XA[]. XB[] array is not referenced.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static double initialdualfeasibilitycorrection(dualsimplexstate state,
+            dualsimplexsubproblem s,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double[] dummy = new double[0];
+            int nn = 0;
+            int m = 0;
+            int ii = 0;
+            int i = 0;
+            int j = 0;
+            bool flipped = new bool();
+            double v = 0;
+            double dj = 0;
+            double xj = 0;
+            int bndt = 0;
+
+            nn = s.ns;
+            m = s.m;
+            alglib.ap.assert(s.state>=ssvalidxn, "InitialDualFeasibilityCorrection: XN is invalid");
+            
+            //
+            // Prepare
+            //
+            apserv.rvectorsetlengthatleast(ref state.dfctmp0, m, _params);
+            apserv.rvectorsetlengthatleast(ref state.dfctmp1, m, _params);
+            
+            //
+            // Recompute D[] using fresh factorization
+            //
+            basisfreshtrf(state.basis, state.at, settings, _params);
+            for(i=0; i<=m-1; i++)
+            {
+                state.dfctmp0[i] = s.effc[state.basis.idx[i]];
+            }
+            basissolvet(state.basis, state.dfctmp0, ref state.dfctmp1, ref state.dfctmp2, _params);
+            computeantv(state, state.dfctmp1, ref s.d, _params);
+            for(i=0; i<=nn-1; i++)
+            {
+                j = state.basis.nidx[i];
+                s.d[j] = s.effc[j]-s.d[j];
+            }
+            
+            //
+            // Perform flips for dual-infeasible boxed variables
+            //
+            result = 0;
+            flipped = false;
+            for(ii=0; ii<=nn-1; ii++)
+            {
+                j = state.basis.nidx[ii];
+                bndt = s.bndt[j];
+                
+                //
+                // Boxed variables, perform DFC
+                //
+                if( bndt==ccrange )
+                {
+                    dj = s.d[j];
+                    xj = s.xa[j];
+                    if( xj==s.bndl[j] && dj<0 )
+                    {
+                        s.xa[j] = s.bndu[j];
+                        flipped = true;
+                        continue;
+                    }
+                    if( xj==s.bndu[j] && dj>0 )
+                    {
+                        s.xa[j] = s.bndl[j];
+                        flipped = true;
+                        continue;
+                    }
+                    continue;
+                }
+                
+                //
+                // Non-boxed variables, compute dual feasibility error
+                //
+                if( bndt==ccfixed )
+                {
+                    continue;
+                }
+                if( bndt==cclower )
+                {
+                    v = -s.d[j];
+                    if( v>result )
+                    {
+                        result = v;
+                    }
+                    continue;
+                }
+                if( bndt==ccupper )
+                {
+                    v = s.d[j];
+                    if( v>result )
+                    {
+                        result = v;
+                    }
+                    continue;
+                }
+                if( bndt==ccfree )
+                {
+                    result = Math.Max(result, Math.Abs(s.d[j]));
+                    continue;
+                }
+            }
+            
+            //
+            // Recompute basic components of X[]
+            //
+            if( flipped || s.state<ssvalid )
+            {
+                computeanxn(state, s, s.xa, ref state.dfctmp0, _params);
+                basissolve(state.basis, state.dfctmp0, ref state.dfctmp1, ref state.dfctmp2, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    s.xa[state.basis.idx[i]] = -state.dfctmp1[i];
+                }
+            }
+            
+            //
+            // Update state validity/age
+            //
+            s.state = ssvalid;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function performs shifting using current algorithm  as  specified  by
+        settings.shifting.
+
+        It accepts following parameters:
+        * AlphaR - pivot row
+        * Delta - delta from pricing step
+        * Q - variable selected by ratio test
+        * ThetaD - dual step length
+
+        If no shifts are necessary, it silently returns. If shifts are  necessary,
+        it modifies ThetaD, S.D, S.EffC according to shifting algorithm.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void shifting(dualsimplexstate state,
+            dualsimplexsubproblem s,
+            double[] alphar,
+            double delta,
+            int q,
+            ref double thetad,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            int dir = 0;
+            double sft = 0;
+            int ii = 0;
+            int j = 0;
+            int bndt = 0;
+
+            
+            //
+            // No shifts
+            //
+            if( settings.shifting==0 )
+            {
+                return;
+            }
+            
+            //
+            // EXPAND with ThetaD=0
+            //
+            if( settings.shifting==1 )
+            {
+                dir = Math.Sign(delta);
+                if( (double)(thetad*dir)>=(double)(0) )
+                {
+                    return;
+                }
+                s.effc[q] = s.effc[q]-s.d[q];
+                s.d[q] = 0;
+                thetad = 0;
+                return;
+            }
+            
+            //
+            // EXPAND with ThetaD=ShiftLen
+            //
+            if( settings.shifting==2 )
+            {
+                dir = Math.Sign(delta);
+                if( (double)(thetad*dir)>(double)(0) )
+                {
+                    return;
+                }
+                
+                //
+                // Ensure that non-zero step is performed
+                //
+                thetad = dir*shiftlen;
+                
+                //
+                // Shift Q-th coefficient
+                //
+                sft = thetad*(dir*alphar[q])-s.d[q];
+                s.effc[q] = s.effc[q]+sft;
+                s.d[q] = s.d[q]+sft;
+                
+                //
+                // Shift other coefficients
+                //
+                for(ii=0; ii<=s.ns-1; ii++)
+                {
+                    j = state.basis.nidx[ii];
+                    bndt = s.bndt[j];
+                    if( (j==q || bndt==ccfixed) || bndt==ccfree )
+                    {
+                        continue;
+                    }
+                    sft = thetad*(dir*alphar[j])-s.d[j];
+                    
+                    //
+                    // Handle variables at lower bound
+                    //
+                    if( bndt==cclower || (bndt==ccrange && s.xa[j]==s.bndl[j]) )
+                    {
+                        sft = sft-dtol;
+                        if( sft>0 )
+                        {
+                            s.effc[j] = s.effc[j]+sft;
+                            s.d[j] = s.d[j]+sft;
+                        }
+                        continue;
+                    }
+                    if( bndt==ccupper || (bndt==ccrange && s.xa[j]==s.bndu[j]) )
+                    {
+                        sft = sft+dtol;
+                        if( sft<0 )
+                        {
+                            s.effc[j] = s.effc[j]+sft;
+                            s.d[j] = s.d[j]+sft;
+                        }
+                        continue;
+                    }
+                }
+                
+                //
+                // Done
+                //
+                return;
+            }
+            alglib.ap.assert(false, "Shifting: unexpected shifting type");
+        }
+
+
+        /*************************************************************************
+        This function performs pricing step
+
+        Additional parameters:
+        * Phase1Pricing - if True, then special Phase #1 restriction is applied to
+          leaving variables: only those are eligible which will move to zero bound
+          after basis change.
+          
+          This trick allows to accelerate and stabilize phase #1. See Robert Fourer,
+          'Notes on the dual simplex method', draft report, 1994, for more info.
+
+        Returns:
+        * leaving variable index P
+        * its index R in the basis, in [0,M) range
+        * Delta - difference between variable value and corresponding bound
+
+        NOTE: this function expects that basic components are stored in XB[];
+              corresponding entries of XA[] are ignored.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void pricingstep(dualsimplexstate state,
+            dualsimplexsubproblem s,
+            bool phase1pricing,
+            ref int p,
+            ref int r,
+            ref double delta,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            int m = 0;
+            int i = 0;
+            int bi = 0;
+            double v = 0;
+            double vtarget = 0;
+            double xbi = 0;
+            double bndl = 0;
+            double bndu = 0;
+            double vdiff = 0;
+            double vtest = 0;
+            double invw = 0;
+            double tol = 0;
+            int bndt = 0;
+            bool hasboth = new bool();
+            bool hasl = new bool();
+            bool hasu = new bool();
+
+            p = 0;
+            r = 0;
+            delta = 0;
+
+            m = s.m;
+            
+            //
+            // Integrity checks
+            //
+            alglib.ap.assert(s.state==ssvalid, "PricingStep: invalid X");
+            alglib.ap.assert(m>0, "PricingStep: M<=0");
+            
+            //
+            // Pricing
+            //
+            if( settings.pricing==0 )
+            {
+                
+                //
+                // "Most infeasible" pricing
+                //
+                // NOTE: VTarget is initialized by XTol because we want to skip
+                //       violations below XTol
+                //
+                p = -1;
+                r = -1;
+                delta = 0;
+                vtarget = xtol;
+                for(i=0; i<=m-1; i++)
+                {
+                    bndt = s.bndtb[i];
+                    hasboth = bndt==3 || bndt==0;
+                    hasl = hasboth || bndt==1;
+                    hasu = hasboth || bndt==2;
+                    xbi = s.xb[i];
+                    if( hasl )
+                    {
+                        bndl = s.bndlb[i];
+                        vdiff = xbi-bndl;
+                        v = -vdiff;
+                        if( v>vtarget )
+                        {
+                            
+                            //
+                            // Special phase 1 pricing: do not choose variables which move to non-zero bound
+                            //
+                            if( phase1pricing && !(bndl==0.0) )
+                            {
+                                continue;
+                            }
+                            
+                            //
+                            // Proceed as usual
+                            //
+                            p = state.basis.idx[i];
+                            r = i;
+                            delta = vdiff;
+                            vtarget = v;
+                            continue;
+                        }
+                    }
+                    if( hasu )
+                    {
+                        bndu = s.bndub[i];
+                        vdiff = xbi-bndu;
+                        v = vdiff;
+                        if( v>vtarget )
+                        {
+                            
+                            //
+                            // Special phase 1 pricing: do not choose variables which move to non-zero bound
+                            //
+                            if( phase1pricing && !(bndu==0.0) )
+                            {
+                                continue;
+                            }
+                            
+                            //
+                            // Proceed as usual
+                            //
+                            p = state.basis.idx[i];
+                            r = i;
+                            delta = vdiff;
+                            vtarget = v;
+                            continue;
+                        }
+                    }
+                }
+                return;
+            }
+            if( settings.pricing==-1 || settings.pricing==1 )
+            {
+                
+                //
+                // Dual steepest edge pricing
+                //
+                basisrequestweights(state.basis, settings, _params);
+                p = -1;
+                r = -1;
+                delta = 0;
+                vtarget = 0;
+                for(i=0; i<=m-1; i++)
+                {
+                    bi = state.basis.idx[i];
+                    bndt = s.bndtb[i];
+                    hasboth = bndt==3 || bndt==0;
+                    hasl = hasboth || bndt==1;
+                    hasu = hasboth || bndt==2;
+                    xbi = s.xb[i];
+                    tol = xtol;
+                    invw = 1/state.basis.dseweights[i];
+                    if( hasl )
+                    {
+                        bndl = s.bndlb[i];
+                        vdiff = xbi-bndl;
+                        vtest = vdiff*vdiff*invw;
+                        if( vdiff<-tol && (p<0 || vtest>vtarget) )
+                        {
+                            
+                            //
+                            // Special phase 1 pricing: do not choose variables which move to non-zero bound
+                            //
+                            if( phase1pricing && !(bndl==0.0) )
+                            {
+                                continue;
+                            }
+                            
+                            //
+                            // Proceed as usual
+                            //
+                            p = bi;
+                            r = i;
+                            delta = vdiff;
+                            vtarget = vtest;
+                            continue;
+                        }
+                    }
+                    if( hasu )
+                    {
+                        bndu = s.bndub[i];
+                        vdiff = xbi-bndu;
+                        vtest = vdiff*vdiff*invw;
+                        if( vdiff>tol && (p<0 || vtest>vtarget) )
+                        {
+                            
+                            //
+                            // Special phase 1 pricing: do not choose variables which move to non-zero bound
+                            //
+                            if( phase1pricing && !(bndu==0.0) )
+                            {
+                                continue;
+                            }
+                            
+                            //
+                            // Proceed as usual
+                            //
+                            p = bi;
+                            r = i;
+                            delta = vdiff;
+                            vtarget = vtest;
+                            continue;
+                        }
+                    }
+                }
+                return;
+            }
+            alglib.ap.assert(false, "PricingStep: unknown pricing type");
+        }
+
+
+        /*************************************************************************
+        This function performs ratio test, either simple one or BFRT.
+
+        It accepts following parameters:
+        * AlphaR - pivot row
+        * Delta - delta from pricing step
+        * P - index of leaving variable from pricing step
+
+        It returns following results:
+        * Q - non-negative value for success, negative for primal infeasible problem
+        * ThetaD - dual step length
+        * PossibleFlips[PossibleFlipsCnt] - for possible flip indexes (for BFRT
+          this set coincides with actual flips, but stabilizing BFRT is a bit more
+          complex - some variables in PossibleFlips[] may need flipping and some not)
+
+        Internally it uses following fields of State for temporaries:
+        * EligibleSet
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void ratiotest(dualsimplexstate state,
+            dualsimplexsubproblem s,
+            double[] alphar,
+            double delta,
+            int p,
+            ref int q,
+            ref double thetad,
+            ref int[] possibleflips,
+            ref int possibleflipscnt,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            int nx = 0;
+            int nn = 0;
+            int i = 0;
+            int j = 0;
+            int nj = 0;
+            int jj = 0;
+            int dir = 0;
+            double sinv = 0;
+            double vx = 0;
+            double vp = 0;
+            double vtarget = 0;
+            double vtest = 0;
+            int eligiblecnt = 0;
+            int bndt = 0;
+            double alphawaver = 0;
+            double adelta = 0;
+            int idx = 0;
+            double vtheta = 0;
+            double thetamax = 0;
+            int harrissetsize = 0;
+            bool hasnonboxedvars = new bool();
+
+            q = 0;
+            thetad = 0;
+
+            nx = s.ns+s.m;
+            nn = s.ns;
+            alglib.ap.assert((double)(delta)!=(double)(0), "RatioTest: zero delta");
+            alglib.ap.assert(s.state==ssvalid, "RatioTest: invalid X");
+            
+            //
+            // Prepare temporaries
+            //
+            // Scaled tolerances are used to test AlphaWaveR for positivity/negativity,
+            // scale of I-th tolerance is calculated as ratio of ColScale[I] and ColScale[P].
+            //
+            dir = Math.Sign(delta);
+            sinv = 1.0/s.colscales[p];
+            apserv.ivectorsetlengthatleast(ref possibleflips, nx, _params);
+            
+            //
+            // Prepare set of eligible variables
+            //
+            // NOTE: free variables are immediately chosen at this stage
+            //
+            apserv.ivectorsetlengthatleast(ref state.eligibleset, nn, _params);
+            eligiblecnt = 0;
+            for(j=0; j<=nn-1; j++)
+            {
+                nj = state.basis.nidx[j];
+                bndt = s.bndt[nj];
+                
+                //
+                // Handle fixed and free variables: fixed ones are not eligible,
+                // free non-basic variables are always and immediately eligible
+                //
+                if( bndt==ccfixed )
+                {
+                    continue;
+                }
+                if( bndt==ccfree )
+                {
+                    q = nj;
+                    thetad = 0;
+                    return;
+                }
+                
+                //
+                // Handle lower/upper/range constraints
+                //
+                vx = s.xa[nj];
+                vp = settings.pivottol*(s.colscales[nj]*sinv);
+                alphawaver = dir*alphar[nj];
+                if( bndt==cclower || (bndt==ccrange && vx==s.bndl[nj]) )
+                {
+                    if( alphawaver>vp )
+                    {
+                        state.eligibleset[eligiblecnt] = nj;
+                        eligiblecnt = eligiblecnt+1;
+                        continue;
+                    }
+                }
+                if( bndt==ccupper || (bndt==ccrange && vx==s.bndu[nj]) )
+                {
+                    if( alphawaver<-vp )
+                    {
+                        state.eligibleset[eligiblecnt] = nj;
+                        eligiblecnt = eligiblecnt+1;
+                        continue;
+                    }
+                }
+            }
+            
+            //
+            // Simple ratio test.
+            //
+            if( settings.ratiotest==0 )
+            {
+                q = -1;
+                thetad = 0;
+                vtarget = 0;
+                possibleflipscnt = 0;
+                for(j=0; j<=eligiblecnt-1; j++)
+                {
+                    nj = state.eligibleset[j];
+                    
+                    //
+                    // More general case
+                    //
+                    alphawaver = dir*alphar[nj];
+                    vtest = s.d[nj]/alphawaver;
+                    if( q<0 || vtest<vtarget )
+                    {
+                        q = nj;
+                        vtarget = vtest;
+                        thetad = s.d[nj]/alphar[nj];
+                    }
+                }
+                shifting(state, s, alphar, delta, q, ref thetad, settings, _params);
+                return;
+            }
+            
+            //
+            // Bounds flipping ratio test
+            //
+            if( settings.ratiotest==1 )
+            {
+                q = -1;
+                thetad = 0;
+                possibleflipscnt = 0;
+                adelta = Math.Abs(delta);
+                
+                //
+                // Quick exit
+                //
+                if( eligiblecnt==0 )
+                {
+                    return;
+                }
+                
+                //
+                // BFRT
+                //
+                while( eligiblecnt>0 )
+                {
+                    
+                    //
+                    // Find Q satisfying BFRT criteria
+                    //
+                    idx = -1;
+                    q = -1;
+                    vtarget = 0;
+                    for(j=0; j<=eligiblecnt-1; j++)
+                    {
+                        nj = state.eligibleset[j];
+                        vtheta = s.d[nj]/alphar[nj];
+                        vtest = dir*vtheta;
+                        if( q<0 || vtest<vtarget )
+                        {
+                            q = nj;
+                            vtarget = vtest;
+                            thetad = vtheta;
+                            idx = j;
+                        }
+                    }
+                    alglib.ap.assert(q>=0, "RatioTest: integrity check failed (BFRT)");
+                    
+                    //
+                    // BFRT mini-iterations will be terminated upon discovery
+                    // of non-boxed variable or upon exhausting of eligible set.
+                    //
+                    if( s.bndt[q]!=ccrange )
+                    {
+                        break;
+                    }
+                    if( eligiblecnt==1 )
+                    {
+                        break;
+                    }
+                    
+                    //
+                    // Update and test ADelta. Break BFRT mini-iterations once
+                    // we get negative slope.
+                    //
+                    adelta = adelta-(s.bndu[q]-s.bndl[q])*Math.Abs(alphar[q]);
+                    if( (double)(adelta)<=(double)(0) )
+                    {
+                        break;
+                    }
+                    
+                    //
+                    // Update eligible set, record flip
+                    //
+                    alglib.ap.assert(state.eligibleset[idx]==q, "RatioTest: unexpected failure");
+                    possibleflips[possibleflipscnt] = q;
+                    possibleflipscnt = possibleflipscnt+1;
+                    state.eligibleset[idx] = state.eligibleset[eligiblecnt-1];
+                    eligiblecnt = eligiblecnt-1;
+                }
+                alglib.ap.assert(q>=0, "RatioTest: unexpected failure");
+                thetad = s.d[q]/alphar[q];
+                shifting(state, s, alphar, delta, q, ref thetad, settings, _params);
+                return;
+            }
+            
+            //
+            // Stabilizing bounds flipping ratio test
+            //
+            if( settings.ratiotest==2 )
+            {
+                q = -1;
+                thetad = 0;
+                possibleflipscnt = 0;
+                adelta = Math.Abs(delta);
+                apserv.ivectorgrowto(ref state.harrisset, eligiblecnt, _params);
+                
+                //
+                // Quick exit
+                //
+                if( eligiblecnt==0 )
+                {
+                    return;
+                }
+                
+                //
+                // BFRT
+                //
+                while( eligiblecnt>0 )
+                {
+                    
+                    //
+                    // Determine ThetaMax according to stabilizing BFRT
+                    //
+                    thetamax = math.maxrealnumber;
+                    for(j=0; j<=eligiblecnt-1; j++)
+                    {
+                        nj = state.eligibleset[j];
+                        alphawaver = dir*alphar[nj];
+                        if( alphawaver>0 )
+                        {
+                            vtest = (s.d[nj]+dtol)/alphawaver;
+                        }
+                        else
+                        {
+                            vtest = (s.d[nj]-dtol)/alphawaver;
+                        }
+                        if( vtest<thetamax )
+                        {
+                            thetamax = vtest;
+                        }
+                    }
+                    
+                    //
+                    // Determine Harris set, entering index Q
+                    // Save flips
+                    //
+                    harrissetsize = 0;
+                    hasnonboxedvars = false;
+                    q = -1;
+                    vtarget = -1;
+                    for(j=0; j<=eligiblecnt-1; j++)
+                    {
+                        nj = state.eligibleset[j];
+                        alphawaver = dir*alphar[nj];
+                        if( s.d[nj]/alphawaver>thetamax )
+                        {
+                            continue;
+                        }
+                        state.harrisset[harrissetsize] = nj;
+                        harrissetsize = harrissetsize+1;
+                        hasnonboxedvars = hasnonboxedvars || s.bndt[nj]!=ccrange;
+                        vtest = Math.Abs(alphawaver);
+                        if( vtest>vtarget )
+                        {
+                            q = nj;
+                            vtarget = vtest;
+                        }
+                    }
+                    alglib.ap.assert(q>=0, "RatioTest: integrity check failed (Harris set selection)");
+                    if( harrissetsize==eligiblecnt )
+                    {
+                        break;
+                    }
+                    
+                    //
+                    // Remove Harris set from the eligible set
+                    //
+                    j = 0;
+                    jj = 0;
+                    for(i=0; i<=eligiblecnt-1; i++)
+                    {
+                        if( j==harrissetsize || state.eligibleset[i]!=state.harrisset[j] )
+                        {
+                            
+                            //
+                            // I-th element not present in Harris set, leave it in the eligible set
+                            //
+                            state.eligibleset[jj] = state.eligibleset[i];
+                            jj = jj+1;
+                        }
+                        else
+                        {
+                            
+                            //
+                            // I-th element is present in Harris set, skip it
+                            //
+                            j = j+1;
+                        }
+                    }
+                    eligiblecnt = eligiblecnt-harrissetsize;
+                    alglib.ap.assert(j==harrissetsize, "RatioTest: integrity check failed");
+                    alglib.ap.assert(jj==eligiblecnt, "RatioTest: integrity check failed");
+                    
+                    //
+                    // Update and test |delta|.
+                    //
+                    // Break BFRT mini-iterations once we get negative slope.
+                    //
+                    for(j=0; j<=harrissetsize-1; j++)
+                    {
+                        nj = state.harrisset[j];
+                        if( !hasnonboxedvars || s.bndt[nj]==ccrange )
+                        {
+                            adelta = adelta-(s.bndu[nj]-s.bndl[nj])*Math.Abs(alphar[nj]);
+                        }
+                        else
+                        {
+                            adelta = -1;
+                        }
+                    }
+                    if( (double)(adelta)<=(double)(0) )
+                    {
+                        break;
+                    }
+                    alglib.ap.assert(!hasnonboxedvars, "RatioTest: integrity check failed");
+                    for(j=0; j<=harrissetsize-1; j++)
+                    {
+                        possibleflips[possibleflipscnt] = state.harrisset[j];
+                        possibleflipscnt = possibleflipscnt+1;
+                    }
+                }
+                alglib.ap.assert(q>=0, "RatioTest: unexpected failure");
+                if( eligiblecnt==0 && adelta<0 )
+                {
+                    
+                    //
+                    // Eligible set exhausted, declare dual unboundedness
+                    //
+                    q = -1;
+                    thetad = 0;
+                    return;
+                }
+                thetad = s.d[q]/alphar[q];
+                shifting(state, s, alphar, delta, q, ref thetad, settings, _params);
+                return;
+            }
+            
+            //
+            // Unknown test type
+            //
+            alglib.ap.assert(false, "RatioTest: integrity check failed, unknown test type");
+        }
+
+
+        /*************************************************************************
+        This function performs update of XB, XN, D and Z during final step of revised
+        dual simplex method.
+
+        It also updates basis cache of the subproblem (s.bcache field).
+
+        Depending on Settings.RatioTest, following operations are performed:
+        * Settings.RatioTest=0  ->  simple update is performed
+        * Settings.RatioTest=1  ->  bounds flipping ratio test update is performed
+        * Settings.RatioTest=2  ->  stabilizing bounds flipping ratio test update is performed
+
+        It accepts following parameters:
+        * P - index of leaving variable from pricing step
+        * Q - index of entering variable.
+        * R - index of leaving variable in AlphaQ
+        * Delta    - delta from pricing step
+        * ThetaP   - primal step length
+        * ThetaD   - dual step length
+        * AlphaQ   - pivot column
+        * AlphaQim - intermediate result from Ftran for AlphaQ, used for
+                     Forest-Tomlin update, not referenced when other update scheme is set
+        * AlphaR   - pivot row
+        * Tau - tau-vector for DSE pricing (ignored if simple pricing is used)
+        * PossibleFlips, PossibleFlipsCnt - outputs of the RatioTest(), information
+          about possible variable flips (however, we have to check residual costs
+          before actually flipping variables - it is possible that some variables
+          in this set actually do not need flipping)
+
+        It performs following operations:
+        * basis update
+        * update of XB/BndTB/BndLB/BndUB[] and XA[] (basic and nonbasic components), D
+        * update of pricing weights
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void updatestep(dualsimplexstate state,
+            dualsimplexsubproblem s,
+            int p,
+            int q,
+            int r,
+            double delta,
+            double thetap,
+            double thetad,
+            double[] alphaq,
+            double[] alphaqim,
+            double[] alphar,
+            double[] tau,
+            int[] possibleflips,
+            int possibleflipscnt,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            int nn = 0;
+            int nx = 0;
+            int m = 0;
+            int ii = 0;
+            int j = 0;
+            int k = 0;
+            int k0 = 0;
+            int k1 = 0;
+            double bndl = 0;
+            double bndu = 0;
+            bool flipped = new bool();
+            double flip = 0;
+            double dj = 0;
+            int dir = 0;
+            int idx = 0;
+            int actualflipscnt = 0;
+
+            nn = s.ns;
+            nx = s.ns+s.m;
+            m = s.m;
+            
+            //
+            // Integrity checks
+            //
+            alglib.ap.assert((settings.ratiotest==0 || settings.ratiotest==1) || settings.ratiotest==2, "UpdateStep: invalid X");
+            alglib.ap.assert(s.state==ssvalid, "UpdateStep: invalid X");
+            alglib.ap.assert(p>=0 && q>=0, "UpdateStep: invalid P/Q");
+            alglib.ap.assert((double)(delta)!=(double)(0), "UpdateStep: Delta=0");
+            alglib.ap.assert((double)(alphaq[r])!=(double)(0), "UpdateStep: AlphaQ[R]=0");
+            
+            //
+            // Prepare
+            //
+            dir = Math.Sign(delta);
+            flip = 0;
+            apserv.rvectorsetlengthatleast(ref state.tmp0, m, _params);
+            for(k=0; k<=m-1; k++)
+            {
+                state.tmp0[k] = 0;
+            }
+            apserv.ivectorsetlengthatleast(ref state.ustmpi, nx, _params);
+            actualflipscnt = 0;
+            
+            //
+            // Evaluate and update non-basic elements of D
+            //
+            for(ii=0; ii<=nn-1; ii++)
+            {
+                j = state.basis.nidx[ii];
+                s.d[j] = s.d[j]-thetad*state.alphar[j];
+            }
+            for(ii=0; ii<=possibleflipscnt-1; ii++)
+            {
+                j = possibleflips[ii];
+                dj = s.d[j];
+                bndl = s.bndl[j];
+                bndu = s.bndu[j];
+                flipped = false;
+                if( s.xa[j]==bndl && dj<0 )
+                {
+                    flip = bndu-bndl;
+                    flipped = true;
+                }
+                else
+                {
+                    if( s.xa[j]==bndu && dj>0 )
+                    {
+                        flip = bndl-bndu;
+                        flipped = true;
+                    }
+                }
+                if( flipped )
+                {
+                    delta = delta-dir*(bndu-bndl)*Math.Abs(alphar[j]);
+                    state.ustmpi[actualflipscnt] = j;
+                    actualflipscnt = actualflipscnt+1;
+                    k0 = state.at.ridx[j];
+                    k1 = state.at.ridx[j+1]-1;
+                    for(k=k0; k<=k1; k++)
+                    {
+                        idx = state.at.idx[k];
+                        state.tmp0[idx] = state.tmp0[idx]+flip*state.at.vals[k];
+                    }
+                }
+            }
+            s.d[p] = -thetad;
+            s.d[q] = 0.0;
+            
+            //
+            // Apply BFRT update (aka long dual step) or simple ratio update
+            //
+            if( actualflipscnt>0 )
+            {
+                thetap = delta/state.alphaq[r];
+                k0 = state.at.ridx[q];
+                k1 = state.at.ridx[q+1]-1;
+                for(k=k0; k<=k1; k++)
+                {
+                    idx = state.at.idx[k];
+                    state.tmp0[idx] = state.tmp0[idx]+thetap*state.at.vals[k];
+                }
+                basissolve(state.basis, state.tmp0, ref state.tmp1, ref state.tmp2, _params);
+                for(j=0; j<=m-1; j++)
+                {
+                    s.xb[j] = s.xb[j]-state.tmp1[j];
+                }
+                for(ii=0; ii<=actualflipscnt-1; ii++)
+                {
+                    j = state.ustmpi[ii];
+                    if( s.xa[j]==s.bndl[j] )
+                    {
+                        s.xa[j] = s.bndu[j];
+                    }
+                    else
+                    {
+                        s.xa[j] = s.bndl[j];
+                    }
+                }
+                s.xb[r] = s.xa[q]+thetap;
+                if( dir<0 )
+                {
+                    s.xa[p] = s.bndl[p];
+                }
+                else
+                {
+                    s.xa[p] = s.bndu[p];
+                }
+            }
+            else
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    s.xb[j] = s.xb[j]-thetap*state.alphaq[j];
+                }
+                s.xb[r] = s.xa[q]+thetap;
+                if( dir<0 )
+                {
+                    s.xa[p] = s.bndl[p];
+                }
+                else
+                {
+                    s.xa[p] = s.bndu[p];
+                }
+            }
+            
+            //
+            // Update basis
+            //
+            basisupdatetrf(state.basis, state.at, p, q, state.alphaq, state.alphaqim, r, tau, settings, _params);
+            
+            //
+            // Update cached variables
+            //
+            s.bndlb[r] = s.bndl[q];
+            s.bndub[r] = s.bndu[q];
+            s.bndtb[r] = s.bndt[q];
+        }
+
+
+        /*************************************************************************
+        This function performs several checks for accumulation of errors during
+        factorization update. It returns True if refactorization is advised.
+
+          -- ALGLIB --
+             Copyright 24.01.2019 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool refactorizationrequired(dualsimplexstate state,
+            dualsimplexsubproblem s,
+            int q,
+            int r,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int m = 0;
+            int i = 0;
+            double mx = 0;
+            double v = 0;
+
+            m = s.m;
+            result = false;
+            
+            //
+            // Quick exit
+            //
+            if( state.basis.trfage<=safetrfage )
+            {
+                return result;
+            }
+            
+            //
+            // Compare Q-th entry of the pivot row AlphaR with R-th entry of the AlphaQ;
+            // ideally, both should match exactly. The difference is a rough estimate
+            // of the magnitude of the numerical errors.
+            //
+            mx = 0.0;
+            for(i=0; i<=m-1; i++)
+            {
+                v = state.alphaq[i];
+                v = v*v;
+                if( v>mx )
+                {
+                    mx = v;
+                }
+            }
+            mx = Math.Sqrt(mx);
+            result = result || (double)(Math.Abs(state.alphaq[r]-state.alphar[q]))>(double)(alphatrigger*(1.0+mx));
+            result = result || (double)(Math.Abs(state.alphaq[r]-state.alphar[q]))>(double)(alphatrigger2*Math.Abs(state.alphar[q]));
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function performs actual solution of dual simplex subproblem  (either
+        primary one or phase 1 one).
+
+        A problem with following properties is expected:
+        * M>0
+        * feasible box constraints
+        * dual feasible initial basis
+        * actual initial point XC and target value Z
+        * actual reduced cost vector D
+        * pricing weights being set to 1.0 or copied from previous problem
+
+        Returns:
+            * Info = +1 for success, -3 for infeasible
+            * IterationsCount is increased by amount of iterations performed
+
+        NOTE: this function internally uses separate storage of basic and nonbasic
+              components; however, all inputs and outputs use single array S.XA[]
+              to store both basic and nonbasic variables. It transparently splits
+              variables on input and recombines them on output.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void solvesubproblemdual(dualsimplexstate state,
+            dualsimplexsubproblem s,
+            bool isphase1,
+            dualsimplexsettings settings,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int nx = 0;
+            int m = 0;
+            int i = 0;
+            int j = 0;
+            int p = 0;
+            int r = 0;
+            int q = 0;
+            double thetad = 0;
+            double thetap = 0;
+            double delta = 0;
+            int forcedrestarts = 0;
+            int j0 = 0;
+            int j1 = 0;
+
+            info = 0;
+
+            nx = s.ns+s.m;
+            m = s.m;
+            forcedrestarts = 0;
+            
+            //
+            // Integrity checks
+            //
+            alglib.ap.assert(s.state==ssvalid, "SolveSubproblemDual: X is not valid");
+            alglib.ap.assert(m>0, "SolveSubproblemDual: M<=0");
+            for(i=0; i<=nx-1; i++)
+            {
+                alglib.ap.assert(s.bndt[i]!=ccinfeasible, "SolveSubproblemDual: infeasible box constraints");
+            }
+            alglib.ap.assert(isdualfeasible(state, s, _params), "SolveSubproblemDual: dual infeasible initial basis");
+            
+            //
+            // Actual processing
+            //
+            offloadbasiccomponents(s, state.basis, _params);
+            info = 0;
+            apserv.rvectorsetlengthatleast(ref state.tmp0, m, _params);
+            while( true )
+            {
+                
+                //
+                // Pricing
+                //
+                pricingstep(state, s, isphase1, ref p, ref r, ref delta, settings, _params);
+                if( (double)(delta)==(double)(0) )
+                {
+                    
+                    //
+                    // Solved! Feasible and bounded!
+                    //
+                    recombinebasicnonbasicx(s, state.basis, _params);
+                    info = 1;
+                    return;
+                }
+                
+                //
+                // BTran
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    state.tmp0[i] = 0;
+                }
+                state.tmp0[r] = 1;
+                basissolvet(state.basis, state.tmp0, ref state.rhor, ref state.tmp2, _params);
+                
+                //
+                // Pivot row
+                //
+                computeantv(state, state.rhor, ref state.alphar, _params);
+                
+                //
+                // Ratio test
+                //
+                ratiotest(state, s, state.alphar, delta, p, ref q, ref thetad, ref state.possibleflips, ref state.possibleflipscnt, settings, _params);
+                if( q<0 )
+                {
+                    
+                    //
+                    // Do we have fresh factorization and state? If not,
+                    // refresh them prior to declaring that we have no solution.
+                    //
+                    if( state.basis.trfage>0 && forcedrestarts<maxforcedrestarts )
+                    {
+                        basisfreshtrf(state.basis, state.at, settings, _params);
+                        subproblemhandlexnupdate(state, s, _params);
+                        offloadbasiccomponents(s, state.basis, _params);
+                        apserv.inc(ref forcedrestarts, _params);
+                        continue;
+                    }
+                    
+                    //
+                    // Dual unbounded, primal infeasible
+                    //
+                    recombinebasicnonbasicx(s, state.basis, _params);
+                    info = -3;
+                    return;
+                }
+                
+                //
+                // FTran
+                //
+                // NOTE: AlphaQim is filled by intermediate FTran result which is useful
+                //       for Forest-Tomlin update scheme. If not Forest-Tomlin update is
+                //       used, then it is not set.
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    state.tmp0[i] = 0;
+                }
+                j0 = state.at.ridx[q];
+                j1 = state.at.ridx[q+1]-1;
+                for(j=j0; j<=j1; j++)
+                {
+                    state.tmp0[state.at.idx[j]] = state.at.vals[j];
+                }
+                basissolvex(state.basis, state.tmp0, ref state.alphaq, ref state.alphaqim, true, ref state.tmp2, _params);
+                
+                //
+                // Check numerical accuracy, trigger refactorization if needed
+                //
+                if( refactorizationrequired(state, s, q, r, _params) )
+                {
+                    basisfreshtrf(state.basis, state.at, settings, _params);
+                    subproblemhandlexnupdate(state, s, _params);
+                    offloadbasiccomponents(s, state.basis, _params);
+                    continue;
+                }
+                
+                //
+                // One more FTRan for DSE weights (if needed)
+                //
+                alglib.ap.assert((settings.pricing==-1 || settings.pricing==0) || settings.pricing==1, "SolveSubproblemDual: unexpected Settings.Pricing");
+                if( settings.pricing==1 )
+                {
+                    basissolve(state.basis, state.rhor, ref state.tau, ref state.tmp2, _params);
+                }
+                
+                //
+                // Basis change and update
+                //
+                thetap = delta/state.alphaq[r];
+                updatestep(state, s, p, q, r, delta, thetap, thetad, state.alphaq, state.alphaqim, state.alphar, state.tau, state.possibleflips, state.possibleflipscnt, settings, _params);
+                apserv.inc(ref state.repiterationscount, _params);
+                if( isphase1 )
+                {
+                    apserv.inc(ref state.repiterationscount1, _params);
+                }
+                else
+                {
+                    apserv.inc(ref state.repiterationscount2, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function solves simplex subproblem using primal simplex method.
+
+        A problem with following properties is expected:
+        * M>0
+        * feasible box constraints
+        * primal feasible initial basis
+        * actual initial point XC and target value Z
+        * actual reduced cost vector D
+        * pricing weights being set to 1.0 or copied from previous problem
+
+        Returns:
+            * Info = +1 for success, -3 for infeasible
+            * IterationsCount is increased by amount of iterations performed
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void solvesubproblemprimal(dualsimplexstate state,
+            dualsimplexsubproblem s,
+            dualsimplexsettings settings,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int nn = 0;
+            int nx = 0;
+            int m = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double vmax = 0;
+            int bi = 0;
+            double dj = 0;
+            int bndt = 0;
+            int q = 0;
+            int p = 0;
+            int r = 0;
+            int dir = 0;
+            double lim = 0;
+            bool haslim = new bool();
+            double thetap = 0;
+            double xbnd = 0;
+            double flip = 0;
+            int canddir = 0;
+            double candlim = 0;
+            double candflip = 0;
+            int j0 = 0;
+            int j1 = 0;
+            double alphawave = 0;
+            double vp = 0;
+            double vb = 0;
+            double vx = 0;
+            double vtest = 0;
+            double vv = 0;
+
+            info = 0;
+
+            nn = s.ns;
+            nx = s.ns+s.m;
+            m = s.m;
+            
+            //
+            // Integrity checks
+            //
+            alglib.ap.assert(s.state==ssvalid, "SolveSubproblemPrimal: X is not valid");
+            alglib.ap.assert(m>0, "SolveSubproblemPrimal: M<=0");
+            for(i=0; i<=nx-1; i++)
+            {
+                alglib.ap.assert(s.bndt[i]!=ccinfeasible, "SolveSubproblemPrimal: infeasible box constraints");
+            }
+            
+            //
+            // Actual processing
+            //
+            info = 1;
+            apserv.rvectorsetlengthatleast(ref state.tmp0, m, _params);
+            while( true )
+            {
+                
+                //
+                // Primal simplex pricing step: we implement the very basic version
+                // of the pricing step because it is expected that primal simplex method
+                // is used just to apply quick correction after removal of the perturbation.
+                //
+                q = -1;
+                vmax = 0;
+                dir = 0;
+                lim = math.maxrealnumber;
+                haslim = false;
+                flip = 0;
+                canddir = 0;
+                for(i=0; i<=nn-1; i++)
+                {
+                    j = state.basis.nidx[i];
+                    dj = s.d[j];
+                    bndt = s.bndt[j];
+                    if( bndt==ccfixed )
+                    {
+                        continue;
+                    }
+                    if( bndt==ccrange )
+                    {
+                        v = 0;
+                        candlim = s.bndu[j]-s.bndl[j];
+                        candflip = 0;
+                        if( s.xa[j]==s.bndl[j] )
+                        {
+                            v = -dj;
+                            canddir = 1;
+                            candflip = s.bndu[j];
+                        }
+                        if( s.xa[j]==s.bndu[j] )
+                        {
+                            v = dj;
+                            canddir = -1;
+                            candflip = s.bndl[j];
+                        }
+                        if( v>vmax )
+                        {
+                            vmax = v;
+                            dir = canddir;
+                            lim = candlim;
+                            haslim = true;
+                            flip = candflip;
+                            q = j;
+                        }
+                        continue;
+                    }
+                    v = 0;
+                    canddir = 0;
+                    if( bndt==cclower )
+                    {
+                        v = -dj;
+                        canddir = 1;
+                    }
+                    if( bndt==ccupper )
+                    {
+                        v = dj;
+                        canddir = -1;
+                    }
+                    if( bndt==ccfree )
+                    {
+                        v = Math.Abs(dj);
+                        canddir = -Math.Sign(dj);
+                    }
+                    if( v>vmax )
+                    {
+                        vmax = v;
+                        dir = canddir;
+                        lim = math.maxrealnumber;
+                        haslim = false;
+                        q = j;
+                    }
+                    continue;
+                }
+                if( vmax<=dtol )
+                {
+                    
+                    //
+                    // Solved: primal and dual feasible!
+                    //
+                    return;
+                }
+                alglib.ap.assert(q>=0, "SolveSubproblemPrimal: integrity check failed");
+                
+                //
+                // FTran and textbook ratio test (again, we expect primal phase to terminate quickly)
+                //
+                // NOTE: AlphaQim is filled by intermediate FTran result which is useful
+                //       for Forest-Tomlin update scheme. If not Forest-Tomlin update is
+                //       used, then it is not set.
+                //
+                for(i=0; i<=m-1; i++)
+                {
+                    state.tmp0[i] = 0;
+                }
+                j0 = state.at.ridx[q];
+                j1 = state.at.ridx[q+1]-1;
+                for(j=j0; j<=j1; j++)
+                {
+                    state.tmp0[state.at.idx[j]] = state.at.vals[j];
+                }
+                basissolvex(state.basis, state.tmp0, ref state.alphaq, ref state.alphaqim, true, ref state.tmp2, _params);
+                vp = settings.pivottol;
+                p = -1;
+                r = -1;
+                thetap = 0;
+                xbnd = 0;
+                for(i=0; i<=m-1; i++)
+                {
+                    bi = state.basis.idx[i];
+                    alphawave = -(dir*state.alphaq[i]);
+                    vx = s.xa[bi];
+                    if( alphawave<-vp && hasbndl(s, bi, _params) )
+                    {
+                        vb = s.bndl[bi];
+                        if( vx<=vb )
+                        {
+                            
+                            //
+                            // X[Bi] is already out of bounds due to rounding errors, perform shifting
+                            //
+                            vb = vx-shiftlen;
+                            s.bndl[bi] = vx;
+                        }
+                        vtest = (vb-vx)/alphawave;
+                        if( p<0 || vtest<thetap )
+                        {
+                            p = bi;
+                            r = i;
+                            thetap = vtest;
+                            xbnd = vb;
+                        }
+                    }
+                    if( alphawave>vp && hasbndu(s, bi, _params) )
+                    {
+                        vb = s.bndu[bi];
+                        if( vx>=vb )
+                        {
+                            
+                            //
+                            // X[Bi] is already out of bounds due to rounding errors, perform shifting
+                            //
+                            vb = vx+shiftlen;
+                            s.bndu[bi] = vb;
+                        }
+                        vtest = (vb-vx)/alphawave;
+                        if( p<0 || vtest<thetap )
+                        {
+                            p = bi;
+                            r = i;
+                            thetap = vtest;
+                            xbnd = vb;
+                        }
+                    }
+                }
+                if( p<0 && !haslim )
+                {
+                    
+                    //
+                    // Primal unbounded
+                    //
+                    info = -4;
+                    return;
+                }
+                
+                //
+                // Update step
+                //
+                if( p>=0 && (!haslim || thetap<lim) )
+                {
+                    
+                    //
+                    // One of the basic variables hit the boundary and become non-basic.
+                    //
+                    // Perform update:
+                    // * update basic elements of X[] (X[p] is explicitly set to the
+                    //   boundary value) and X[q]
+                    // * update target value Z
+                    // * update factorization
+                    // * update D[]
+                    //
+                    apserv.rvectorsetlengthatleast(ref state.tmp0, m, _params);
+                    for(i=0; i<=m-1; i++)
+                    {
+                        bi = state.basis.idx[i];
+                        vv = thetap*(dir*state.alphaq[i]);
+                        s.xa[bi] = s.xa[bi]-vv;
+                    }
+                    s.xa[p] = xbnd;
+                    s.xa[q] = s.xa[q]+dir*thetap;
+                    for(i=0; i<=m-1; i++)
+                    {
+                        state.tmp0[i] = 0;
+                    }
+                    basisupdatetrf(state.basis, state.at, p, q, state.alphaq, state.alphaqim, r, state.tmp0, settings, _params);
+                    for(i=0; i<=m-1; i++)
+                    {
+                        state.tmp0[i] = s.effc[state.basis.idx[i]];
+                    }
+                    basissolvet(state.basis, state.tmp0, ref state.tmp1, ref state.tmp2, _params);
+                    computeantv(state, state.tmp1, ref s.d, _params);
+                    for(i=0; i<=nn-1; i++)
+                    {
+                        j = state.basis.nidx[i];
+                        s.d[j] = s.effc[j]-s.d[j];
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Basis does not change because Qth variable flips from one bound
+                    // to another one long before we encounter the boundary
+                    //
+                    s.xa[q] = flip;
+                    for(i=0; i<=m-1; i++)
+                    {
+                        bi = state.basis.idx[i];
+                        vv = lim*(dir*state.alphaq[i]);
+                        s.xa[bi] = s.xa[bi]-vv;
+                    }
+                }
+                apserv.inc(ref state.repiterationscount, _params);
+                apserv.inc(ref state.repiterationscount3, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        This function estimates feasibility properties of the  current  basis  and
+        invokes phase 1 if necessary.
+
+        A problem with following properties is expected:
+        * M>0
+        * feasible box constraints
+        * some initial basis (can be dual infeasible) with actual factorization
+        * actual initial point XC and target value Z
+        * actual reduced cost vector D
+
+        It returns:
+        * +1 if dual feasible basis was found
+        * -4 if problem is dual infeasible
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void invokephase1(dualsimplexstate state,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            int m = 0;
+
+            m = state.primary.m;
+            state.repterminationtype = 0;
+            
+            //
+            // Integrity checks
+            //
+            alglib.ap.assert(state.primary.state==ssvalid, "InvokePhase1: invalid primary X");
+            alglib.ap.assert(m>0, "InvokePhase1: M<=0");
+            
+            //
+            // Is it dual feasible from the very beginning (or maybe after initial DFC)?
+            //
+            if( (double)(initialdualfeasibilitycorrection(state, state.primary, settings, _params))<=(double)(dtol) )
+            {
+                state.repterminationtype = 1;
+                return;
+            }
+            
+            //
+            // Solve phase #1 subproblem
+            //
+            subprobleminitphase1(state.primary, state.basis, state.phase1, _params);
+            initialdualfeasibilitycorrection(state, state.phase1, settings, _params);
+            solvesubproblemdual(state, state.phase1, true, settings, ref state.repterminationtype, _params);
+            alglib.ap.assert(state.repterminationtype>0, "DualSimplexSolver: unexpected failure of phase #1");
+            state.repterminationtype = 1;
+            
+            //
+            // Setup initial basis for phase #2 using solution of phase #1
+            //
+            subprobleminferinitialxn(state, state.primary, _params);
+            if( (double)(initialdualfeasibilitycorrection(state, state.primary, settings, _params))>(double)(dtol) )
+            {
+                state.repterminationtype = -4;
+                return;
+            }
+            state.repterminationtype = 1;
+        }
+
+
+        /*************************************************************************
+        Box-constrained solver; sets State.RepX, State.RepStats and State.RepTerminationType,
+        does not change other fields.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void solveboxonly(dualsimplexstate state,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int ns = 0;
+
+            ns = state.primary.ns;
+            alglib.ap.assert(state.primary.m==0, "SolveBoxOnly: integrity check failed");
+            for(i=0; i<=ns-1; i++)
+            {
+                
+                //
+                // Handle infeasible variable
+                //
+                if( state.primary.bndt[i]==ccinfeasible )
+                {
+                    state.repterminationtype = -3;
+                    setzeroxystats(state, _params);
+                    return;
+                }
+                
+                //
+                // Handle fixed variable
+                //
+                if( state.primary.bndt[i]==ccfixed )
+                {
+                    state.repx[i] = state.primary.bndl[i];
+                    state.repstats[i] = -1;
+                    continue;
+                }
+                
+                //
+                // Handle non-zero cost component
+                //
+                if( (double)(state.primary.rawc[i])>(double)(0) )
+                {
+                    if( state.primary.bndt[i]!=ccrange && state.primary.bndt[i]!=cclower )
+                    {
+                        state.repterminationtype = -4;
+                        setzeroxystats(state, _params);
+                        return;
+                    }
+                    state.repx[i] = state.primary.bndl[i];
+                    state.repstats[i] = -1;
+                    continue;
+                }
+                if( (double)(state.primary.rawc[i])<(double)(0) )
+                {
+                    if( state.primary.bndt[i]!=ccrange && state.primary.bndt[i]!=ccupper )
+                    {
+                        state.repterminationtype = -4;
+                        setzeroxystats(state, _params);
+                        return;
+                    }
+                    state.repx[i] = state.primary.bndu[i];
+                    state.repstats[i] = 1;
+                    continue;
+                }
+                
+                //
+                // Handle non-free variable with zero cost component
+                //
+                if( state.primary.bndt[i]==ccupper || state.primary.bndt[i]==ccrange )
+                {
+                    state.repx[i] = state.primary.bndu[i];
+                    state.repstats[i] = 1;
+                    continue;
+                }
+                if( state.primary.bndt[i]==cclower )
+                {
+                    state.repx[i] = state.primary.bndl[i];
+                    state.repstats[i] = -1;
+                    continue;
+                }
+                
+                //
+                // Free variable, zero cost component
+                //
+                alglib.ap.assert(state.primary.bndt[i]==ccfree, "DSSOptimize: integrity check failed");
+                state.repx[i] = 0;
+                state.repstats[i] = 0;
+            }
+        }
+
+
+        /*************************************************************************
+        Zero-fill RepX, RepY, RepStats.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void setzeroxystats(dualsimplexstate state,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            for(i=0; i<=state.primary.ns-1; i++)
+            {
+                state.repx[i] = 0;
+            }
+            for(i=0; i<=state.primary.m-1; i++)
+            {
+                state.repy[i] = 0;
+                state.repdx[i] = 0;
+            }
+            for(i=0; i<=state.primary.ns+state.primary.m-1; i++)
+            {
+                state.repstats[i] = 0;
+            }
+        }
+
+
+        /*************************************************************************
+        This function initializes basis structure; no triangular factorization is
+        prepared yet. Previously allocated memory is reused.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void basisinit(int ns,
+            int m,
+            dualsimplexbasis s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            s.ns = ns;
+            s.m = m;
+            apserv.ivectorgrowto(ref s.idx, m, _params);
+            apserv.ivectorgrowto(ref s.nidx, ns, _params);
+            apserv.bvectorgrowto(ref s.isbasic, ns+m, _params);
+            for(i=0; i<=ns-1; i++)
+            {
+                s.nidx[i] = i;
+                s.isbasic[i] = false;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                s.idx[i] = ns+i;
+                s.isbasic[ns+i] = true;
+            }
+            s.trftype = 3;
+            s.trfage = 0;
+            s.isvalidtrf = false;
+            apserv.rvectorsetlengthatleast(ref s.dseweights, m, _params);
+            for(i=0; i<=m-1; i++)
+            {
+                s.dseweights[i] = 1.0;
+            }
+            s.dsevalid = false;
+        }
+
+
+        /*************************************************************************
+        This function resizes basis. It is assumed that constraint matrix is
+        completely overwritten by new one, but both matrices are similar enough
+        so we can reuse previous basis.
+
+        Dual steepest edge weights are invalidated by this function.
+
+        This function:
+        * tries to resize basis
+        * if possible, returns True and valid basis with valid factorization
+        * if resize is impossible (or abandoned due to stability reasons), it
+          returns False and basis object is left in the invalid state (you have
+          to reinitialize it by all-logicals basis)
+
+        Following types of resize are supported:
+        * new basis size is larger than previous one => logical elements are
+          added to the new basis
+        * basis sizes match => no operation is performed
+        * new basis size is zero => basis is set to zero
+
+        This function:
+        * requires valid triangular factorization at S on entry
+        * replaces it by another, valid factorization
+        * checks that new factorization deviates from the previous one not too much
+          by comparing magnitudes of min[abs(u_ii)] in both factorization (sharp
+          decrease results in attempt to resize being abandoned
+
+        IMPORTANT: if smooth resize is not possible, this function throws an
+                   exception! It is responsibility of the caller to check that
+                   smooth resize is possible
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool basistryresize(dualsimplexbasis s,
+            int newm,
+            sparse.sparsematrix at,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int ns = 0;
+            int oldm = 0;
+            int i = 0;
+            double oldminu = 0;
+            double newminu = 0;
+
+            ns = s.ns;
+            oldm = s.m;
+            result = false;
+            
+            //
+            // Quick exit strategies
+            //
+            if( newm==0 )
+            {
+                basisinit(ns, 0, s, _params);
+                result = true;
+                return result;
+            }
+            
+            //
+            // Same size or larger
+            //
+            if( newm>=oldm )
+            {
+                alglib.ap.assert(s.isvalidtrf || oldm==0, "BasisTryResize: needs valid TRF in S");
+                
+                //
+                // Save information about matrix conditioning 
+                //
+                oldminu = basisminimumdiagonalelement(s, _params);
+                
+                //
+                // Growth if needed
+                //
+                s.m = newm;
+                apserv.ivectorgrowto(ref s.idx, newm, _params);
+                apserv.bvectorgrowto(ref s.isbasic, ns+newm, _params);
+                for(i=oldm; i<=newm-1; i++)
+                {
+                    s.idx[i] = ns+i;
+                    s.isbasic[ns+i] = true;
+                }
+                
+                //
+                // DSE weights are invalid and filled by 1.0
+                //
+                apserv.rvectorgrowto(ref s.dseweights, newm, _params);
+                for(i=0; i<=newm-1; i++)
+                {
+                    s.dseweights[i] = 1.0;
+                }
+                s.dsevalid = false;
+                
+                //
+                // Invalidate TRF.
+                // Try to refactorize.
+                //
+                s.isvalidtrf = false;
+                newminu = basisfreshtrfunsafe(s, at, settings, _params);
+                result = (double)(newminu)>=(double)(maxudecay*oldminu);
+                return result;
+            }
+            alglib.ap.assert(false, "BasisTryResize: unexpected branch");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function returns minimum diagonal element of S. Result=1 is  returned
+        for M=0.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static double basisminimumdiagonalelement(dualsimplexbasis s,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double v = 0;
+            double vv = 0;
+            int i = 0;
+            int m = 0;
+
+            m = s.m;
+            if( m==0 )
+            {
+                result = 1;
+                return result;
+            }
+            alglib.ap.assert(((s.trftype==0 || s.trftype==1) || s.trftype==2) || s.trftype==3, "BasisMinimumDiagonalElement: unexpected TRF type");
+            alglib.ap.assert(s.isvalidtrf, "BasisMinimumDiagonalElement: TRF is invalid");
+            v = math.maxrealnumber;
+            for(i=0; i<=m-1; i++)
+            {
+                vv = 0;
+                if( s.trftype==0 || s.trftype==1 )
+                {
+                    vv = s.denselu[i,i];
+                }
+                if( s.trftype==2 || s.trftype==3 )
+                {
+                    vv = sparse.sparsegetdiagonal(s.sparseu, i, _params);
+                }
+                if( vv<0 )
+                {
+                    vv = -vv;
+                }
+                if( vv<v )
+                {
+                    v = vv;
+                }
+            }
+            result = v;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function exports division of variables into basic/nonbasic ones; only
+        basic/nonbasic sets are exported - triangular factorization is NOT exported;
+        however, valid triangular factorization IS required in order to perform
+        exporting.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void basisexportto(dualsimplexbasis s0,
+            dualsimplexbasis s1,
+            alglib.xparams _params)
+        {
+            s1.ns = s0.ns;
+            s1.m = s0.m;
+            apserv.copyintegerarray(s0.idx, ref s1.idx, _params);
+            apserv.copyintegerarray(s0.nidx, ref s1.nidx, _params);
+            apserv.copybooleanarray(s0.isbasic, ref s1.isbasic, _params);
+            s1.isvalidtrf = false;
+            s1.trftype = -1;
+            s1.dsevalid = false;
+            if( s0.m>0 )
+            {
+                alglib.ap.assert(s0.isvalidtrf, "BasisExport: valid factorization is required for source basis");
+                s1.eminu = basisminimumdiagonalelement(s0, _params);
+            }
+            else
+            {
+                s1.eminu = 1;
+            }
+        }
+
+
+        /*************************************************************************
+        This function imports from S1 to S0 a division of variables into
+        basic/nonbasic ones; only basic/nonbasic sets are imported.
+
+        Triangular factorization is not imported; however,  this  function  checks
+        that new factorization deviates from the previous  one  not  too  much  by
+        comparing magnitudes of min[abs(u_ii)] in both factorization (basis being
+        imported stores statistics about U). Sharp decrease of diagonal elements
+        means that we have too unstable situation which results in import being
+        abandoned. In this case False is returned, and the basis S0 is left in the
+        indeterminate invalid state (you have to reinitialize it by all-logicals).
+
+        IMPORTANT: if metrics of S0 and S1 do not match, an exception will be generated.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool basistryimportfrom(dualsimplexbasis s0,
+            dualsimplexbasis s1,
+            sparse.sparsematrix at,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+            double newminu = 0;
+
+            alglib.ap.assert(s0.ns==s1.ns, "BasisImportFrom: structural variable counts do not match");
+            s0.m = s1.m;
+            for(i=0; i<=s0.m-1; i++)
+            {
+                s0.idx[i] = s1.idx[i];
+            }
+            for(i=0; i<=s0.ns-1; i++)
+            {
+                s0.nidx[i] = s1.nidx[i];
+            }
+            for(i=0; i<=s0.m+s0.ns-1; i++)
+            {
+                s0.isbasic[i] = s1.isbasic[i];
+            }
+            s0.isvalidtrf = false;
+            apserv.rvectorsetlengthatleast(ref s0.dseweights, s1.m, _params);
+            for(i=0; i<=s1.m-1; i++)
+            {
+                s0.dseweights[i] = 1.0;
+            }
+            s0.dsevalid = false;
+            newminu = basisfreshtrfunsafe(s0, at, settings, _params);
+            result = (double)(newminu)>=(double)(maxudecay*s1.eminu);
+            if( !result )
+            {
+                s0.isvalidtrf = false;
+                s0.trftype = -1;
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function computes fresh triangular factorization.
+
+        If TRF of age 0 (fresh) is already present, no new factorization is calculated.
+        If factorization has exactly zero element along diagonal, this function
+        generates exception.
+
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void basisfreshtrf(dualsimplexbasis s,
+            sparse.sparsematrix at,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            double v = 0;
+
+            v = basisfreshtrfunsafe(s, at, settings, _params);
+            alglib.ap.assert((double)(v)>(double)(0), "BasisFreshTrf: degeneracy of B is detected");
+        }
+
+
+        /*************************************************************************
+        This function computes fresh triangular factorization.
+
+        If TRF of age 0 (fresh) is already present, no new factorization is calculated.
+
+        It returns min[abs(u[i,i])] which can be used to determine whether factorization
+        is degenerate or not (it will factorize anything, the question is whether
+        it is possible to use factorization)
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static double basisfreshtrfunsafe(dualsimplexbasis s,
+            sparse.sparsematrix at,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int m = 0;
+            int ns = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int j0 = 0;
+            int j1 = 0;
+            int k1 = 0;
+            int nzl = 0;
+            int nzu = 0;
+            int nlogical = 0;
+            int nstructural = 0;
+            int offs = 0;
+            int offs1 = 0;
+            int offs2 = 0;
+
+            m = s.m;
+            ns = s.ns;
+            result = 0;
+            
+            //
+            // Compare TRF type with one required by settings, invalidation and refresh otherwise
+            //
+            if( s.trftype!=settings.trftype )
+            {
+                s.trftype = settings.trftype;
+                s.isvalidtrf = false;
+                result = basisfreshtrfunsafe(s, at, settings, _params);
+                return result;
+            }
+            
+            //
+            // Is it valid and fresh?
+            //
+            if( s.isvalidtrf && s.trfage==0 )
+            {
+                result = basisminimumdiagonalelement(s, _params);
+                return result;
+            }
+            
+            //
+            // Dense TRF
+            //
+            if( s.trftype==0 || s.trftype==1 )
+            {
+                apserv.ivectorsetlengthatleast(ref s.colpermbwd, m, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    s.colpermbwd[i] = i;
+                }
+                apserv.rmatrixsetlengthatleast(ref s.denselu, m, m, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        s.denselu[i,j] = 0;
+                    }
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    j0 = at.ridx[s.idx[i]];
+                    j1 = at.ridx[s.idx[i]+1]-1;
+                    for(j=j0; j<=j1; j++)
+                    {
+                        s.denselu[i,at.idx[j]] = at.vals[j];
+                    }
+                }
+                trfac.rmatrixlu(ref s.denselu, m, m, ref s.tmpi, _params);
+                pivottobwd(s.tmpi, m, ref s.rowpermbwd, _params);
+                s.isvalidtrf = true;
+                s.trfage = 0;
+                result = basisminimumdiagonalelement(s, _params);
+                return result;
+            }
+            
+            //
+            // Sparse TRF (with either PFI or Forest-Tomlin)
+            //
+            if( s.trftype==2 || s.trftype==3 )
+            {
+                
+                //
+                // Determine permutation which moves logical variables
+                // to the beginning.
+                //
+                // NOTE: this reordering results in stable factorization
+                //       because we prenormalized constraints with 2-norm,
+                //       all elements in the logical columns are less than
+                //       1.0 in magnitude.
+                //
+                // After this block is done we have following arrays:
+                // * tCInvIdx[j], which is an inverse of ColPermBwf[]
+                //
+                apserv.ivectorsetlengthatleast(ref s.tcinvidx, m, _params);
+                apserv.ivectorsetlengthatleast(ref s.rowpermbwd, m, _params);
+                apserv.ivectorsetlengthatleast(ref s.colpermbwd, m, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    s.tcinvidx[i] = i;
+                    s.rowpermbwd[i] = i;
+                    s.colpermbwd[i] = i;
+                }
+                nlogical = 0;
+                for(i=0; i<=m-1; i++)
+                {
+                    if( s.idx[i]>=ns )
+                    {
+                        j = s.rowpermbwd[nlogical];
+                        s.rowpermbwd[nlogical] = s.rowpermbwd[i];
+                        s.rowpermbwd[i] = j;
+                        j1 = s.tcinvidx[s.idx[i]-ns];
+                        j = s.colpermbwd[j1];
+                        s.colpermbwd[j1] = s.colpermbwd[nlogical];
+                        s.colpermbwd[nlogical] = j;
+                        s.tcinvidx[s.colpermbwd[nlogical]] = nlogical;
+                        s.tcinvidx[s.colpermbwd[j1]] = j1;
+                        nlogical = nlogical+1;
+                    }
+                }
+                tsort.sortmiddlei(s.colpermbwd, nlogical, m-nlogical, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    s.tcinvidx[s.colpermbwd[i]] = i;
+                }
+                nstructural = m-nlogical;
+                
+                //
+                // Prepare SparseLU1 to receive factored out logical part of the matrix
+                // and SparseLU2 to receive structural part of the matrix.
+                //
+                apserv.ivectorsetlengthatleast(ref s.sparselu1.ridx, nstructural+1, _params);
+                apserv.ivectorsetlengthatleast(ref s.sparselu1.didx, nstructural, _params);
+                apserv.ivectorsetlengthatleast(ref s.sparselu1.uidx, nstructural, _params);
+                s.sparselu1.matrixtype = 1;
+                s.sparselu1.m = nstructural;
+                s.sparselu1.n = nlogical;
+                s.sparselu1.ridx[0] = 0;
+                apserv.ivectorsetlengthatleast(ref s.sparselu2.ridx, nstructural+1, _params);
+                apserv.ivectorsetlengthatleast(ref s.sparselu2.didx, nstructural, _params);
+                apserv.ivectorsetlengthatleast(ref s.sparselu2.uidx, nstructural, _params);
+                s.sparselu2.matrixtype = 1;
+                s.sparselu2.m = nstructural;
+                s.sparselu2.n = nstructural;
+                s.sparselu2.ridx[0] = 0;
+                
+                //
+                // Reorder array, perform LU factorization
+                //
+                for(k=0; k<=nstructural-1; k++)
+                {
+                    
+                    //
+                    // Make sure SparseLU1 and SparseLU2 have enough place.
+                    //
+                    offs1 = s.sparselu1.ridx[k];
+                    offs2 = s.sparselu2.ridx[k];
+                    apserv.ivectorgrowto(ref s.sparselu1.idx, offs1+m, _params);
+                    apserv.rvectorgrowto(ref s.sparselu1.vals, offs1+m, _params);
+                    apserv.ivectorgrowto(ref s.sparselu2.idx, offs2+m, _params);
+                    apserv.rvectorgrowto(ref s.sparselu2.vals, offs2+m, _params);
+                    
+                    //
+                    // Extract K-th row of the SparseLU1/2 (I-th row of the original matrix)
+                    //
+                    i = s.rowpermbwd[k+nlogical];
+                    j0 = at.ridx[s.idx[i]];
+                    j1 = at.ridx[s.idx[i]+1]-1;
+                    for(j=j0; j<=j1; j++)
+                    {
+                        k1 = s.tcinvidx[at.idx[j]];
+                        if( k1<nlogical )
+                        {
+                            
+                            //
+                            // Append element to SparseLU1
+                            //
+                            s.sparselu1.idx[offs1] = k1;
+                            s.sparselu1.vals[offs1] = at.vals[j];
+                            offs1 = offs1+1;
+                        }
+                        else
+                        {
+                            
+                            //
+                            // Append element to SparseLU2
+                            //
+                            s.sparselu2.idx[offs2] = k1-nlogical;
+                            s.sparselu2.vals[offs2] = at.vals[j];
+                            offs2 = offs2+1;
+                        }
+                    }
+                    
+                    //
+                    // Elements added to the last row of LU1 can be unordered,
+                    // so it needs resorting.
+                    //
+                    // LU2 does NOT need resorting because trailing NStructural
+                    // elements of permutation were post-sorted to produce
+                    // already sorted results.
+                    //
+                    tsort.tagsortmiddleir(ref s.sparselu1.idx, ref s.sparselu1.vals, s.sparselu1.ridx[k], offs1-s.sparselu1.ridx[k], _params);
+                    s.sparselu1.ridx[k+1] = offs1;
+                    s.sparselu2.ridx[k+1] = offs2;
+                }
+                s.sparselu1.ninitialized = s.sparselu1.ridx[nstructural];
+                s.sparselu2.ninitialized = s.sparselu2.ridx[nstructural];
+                sparse.sparseinitduidx(s.sparselu1, _params);
+                sparse.sparseinitduidx(s.sparselu2, _params);
+                if( nstructural>0 )
+                {
+                    sptrf.sptrflu(s.sparselu2, 2, ref s.densep2, ref s.densep2c, s.lubuf2, _params);
+                    for(i=0; i<=nstructural-1; i++)
+                    {
+                        j = s.rowpermbwd[i+nlogical];
+                        s.rowpermbwd[i+nlogical] = s.rowpermbwd[s.densep2[i]+nlogical];
+                        s.rowpermbwd[s.densep2[i]+nlogical] = j;
+                        j = s.colpermbwd[i+nlogical];
+                        s.colpermbwd[i+nlogical] = s.colpermbwd[s.densep2c[i]+nlogical];
+                        s.colpermbwd[s.densep2c[i]+nlogical] = j;
+                    }
+                    
+                    //
+                    // Process L factor:
+                    //
+                    // 1. count number of non-zeros in the L factor,
+                    // 2. fill NLogical*NLogical leading block
+                    // 3. NStructural*M bottom block
+                    //
+                    nzl = nlogical;
+                    for(i=0; i<=nstructural-1; i++)
+                    {
+                        k = s.lubuf2.rowpermrawidx[i];
+                        nzl = nzl+(s.sparselu1.ridx[k+1]-s.sparselu1.ridx[k]);
+                        nzl = nzl+1+(s.sparselu2.didx[i]-s.sparselu2.ridx[i]);
+                    }
+                    apserv.rvectorsetlengthatleast(ref s.sparsel.vals, nzl, _params);
+                    apserv.ivectorsetlengthatleast(ref s.sparsel.idx, nzl, _params);
+                    apserv.ivectorsetlengthatleast(ref s.sparsel.ridx, m+1, _params);
+                    apserv.ivectorsetlengthatleast(ref s.sparsel.didx, m, _params);
+                    apserv.ivectorsetlengthatleast(ref s.sparsel.uidx, m, _params);
+                    s.sparsel.matrixtype = 1;
+                    s.sparsel.m = m;
+                    s.sparsel.n = m;
+                    s.sparsel.ninitialized = nzl;
+                    s.sparsel.ridx[0] = 0;
+                    for(i=0; i<=nlogical-1; i++)
+                    {
+                        s.sparsel.idx[i] = i;
+                        s.sparsel.vals[i] = 1.0;
+                        s.sparsel.ridx[i+1] = i+1;
+                    }
+                    for(i=0; i<=nstructural-1; i++)
+                    {
+                        offs = s.sparsel.ridx[nlogical+i];
+                        k = s.lubuf2.rowpermrawidx[i];
+                        j0 = s.sparselu1.ridx[k];
+                        j1 = s.sparselu1.ridx[k+1]-1;
+                        for(j=j0; j<=j1; j++)
+                        {
+                            s.sparsel.idx[offs] = s.sparselu1.idx[j];
+                            s.sparsel.vals[offs] = -s.sparselu1.vals[j];
+                            offs = offs+1;
+                        }
+                        j0 = s.sparselu2.ridx[i];
+                        j1 = s.sparselu2.didx[i]-1;
+                        for(j=j0; j<=j1; j++)
+                        {
+                            s.sparsel.idx[offs] = nlogical+s.sparselu2.idx[j];
+                            s.sparsel.vals[offs] = s.sparselu2.vals[j];
+                            offs = offs+1;
+                        }
+                        s.sparsel.idx[offs] = nlogical+i;
+                        s.sparsel.vals[offs] = 1.0;
+                        offs = offs+1;
+                        s.sparsel.ridx[nlogical+i+1] = offs;
+                    }
+                    alglib.ap.assert(s.sparsel.ninitialized==s.sparsel.ridx[m], "BasisFreshTrf: integrity check failed");
+                    sparse.sparseinitduidx(s.sparsel, _params);
+                    
+                    //
+                    // Process U factor:
+                    //
+                    // 1. count number of non-zeros in the U factor,
+                    // 2. fill NLogical*NLogical leading block
+                    // 3. NStructural*NStructural bottom block
+                    //
+                    nzu = nlogical;
+                    for(i=0; i<=nstructural-1; i++)
+                    {
+                        nzu = nzu+1+(s.sparselu2.ridx[i+1]-s.sparselu2.uidx[i]);
+                    }
+                    apserv.rvectorsetlengthatleast(ref s.sparseu.vals, nzu, _params);
+                    apserv.ivectorsetlengthatleast(ref s.sparseu.idx, nzu, _params);
+                    apserv.ivectorsetlengthatleast(ref s.sparseu.ridx, m+1, _params);
+                    apserv.ivectorsetlengthatleast(ref s.sparseu.didx, m, _params);
+                    apserv.ivectorsetlengthatleast(ref s.sparseu.uidx, m, _params);
+                    s.sparseu.matrixtype = 1;
+                    s.sparseu.m = m;
+                    s.sparseu.n = m;
+                    s.sparseu.ninitialized = nzu;
+                    s.sparseu.ridx[0] = 0;
+                    for(i=0; i<=nlogical-1; i++)
+                    {
+                        s.sparseu.idx[i] = i;
+                        s.sparseu.vals[i] = -1.0;
+                        s.sparseu.ridx[i+1] = i+1;
+                    }
+                    for(i=0; i<=nstructural-1; i++)
+                    {
+                        offs = s.sparseu.ridx[nlogical+i];
+                        s.sparseu.idx[offs] = nlogical+i;
+                        j = s.sparselu2.didx[i];
+                        if( j<s.sparselu2.uidx[i] )
+                        {
+                            alglib.ap.assert(s.sparselu2.idx[j]==i, "BasisFreshTrf: integrity check failed");
+                            s.sparseu.vals[offs] = s.sparselu2.vals[j];
+                        }
+                        else
+                        {
+                            s.sparseu.vals[offs] = 0;
+                        }
+                        offs = offs+1;
+                        j0 = s.sparselu2.uidx[i];
+                        j1 = s.sparselu2.ridx[i+1]-1;
+                        for(j=j0; j<=j1; j++)
+                        {
+                            s.sparseu.idx[offs] = nlogical+s.sparselu2.idx[j];
+                            s.sparseu.vals[offs] = s.sparselu2.vals[j];
+                            offs = offs+1;
+                        }
+                        s.sparseu.ridx[nlogical+i+1] = offs;
+                    }
+                    alglib.ap.assert(s.sparseu.ninitialized==s.sparseu.ridx[m], "BasisFreshTrf: integrity check failed");
+                    sparse.sparseinitduidx(s.sparseu, _params);
+                }
+                else
+                {
+                    apserv.ivectorsetlengthatleast(ref s.nrs, m, _params);
+                    for(i=0; i<=m-1; i++)
+                    {
+                        s.nrs[i] = 1;
+                    }
+                    sparse.sparsecreatecrsbuf(m, m, s.nrs, s.sparsel, _params);
+                    for(i=0; i<=nlogical-1; i++)
+                    {
+                        sparse.sparseset(s.sparsel, i, i, 1.0, _params);
+                    }
+                    sparse.sparsecreatecrsbuf(m, m, s.nrs, s.sparseu, _params);
+                    for(i=0; i<=nlogical-1; i++)
+                    {
+                        sparse.sparseset(s.sparseu, i, i, -1.0, _params);
+                    }
+                }
+                sparse.sparsecopytransposecrsbuf(s.sparseu, s.sparseut, _params);
+                s.isvalidtrf = true;
+                s.trfage = 0;
+                result = basisminimumdiagonalelement(s, _params);
+                return result;
+            }
+            
+            //
+            //
+            //
+            alglib.ap.assert(false, "BasisFreshTrf: unexpected TRF type");
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function fills S.DSEWeights by actual weights according to current
+        settings and sets validity flag.
+
+        Basis object MUST store valid triangular factorization, otherwise this
+        function throws an exception.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void basisrequestweights(dualsimplexbasis s,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            int m = 0;
+            int ns = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double vv = 0;
+
+            m = s.m;
+            ns = s.ns;
+            alglib.ap.assert((settings.pricing==-1 || settings.pricing==0) || settings.pricing==1, "BasisRequestWeights: unknown pricing type");
+            alglib.ap.assert(s.isvalidtrf, "BasisRequestWeights: factorization is not computed prior to calling this function");
+            
+            //
+            // If weights are valid, return immediately
+            //
+            if( s.dsevalid )
+            {
+                return;
+            }
+            
+            //
+            // Compute weights from scratch
+            //
+            if( settings.pricing==-1 || settings.pricing==1 )
+            {
+                for(i=0; i<=m-1; i++)
+                {
+                    if( s.idx[i]<ns )
+                    {
+                        
+                        //
+                        // Structural variable, DSE weight is computed by definition
+                        //
+                        apserv.rvectorsetlengthatleast(ref s.wtmp0, m, _params);
+                        apserv.rvectorsetlengthatleast(ref s.wtmp1, m, _params);
+                        for(j=0; j<=m-1; j++)
+                        {
+                            s.wtmp0[j] = 0;
+                        }
+                        s.wtmp0[i] = 1;
+                        basissolvet(s, s.wtmp0, ref s.wtmp1, ref s.wtmp2, _params);
+                        v = 0;
+                        for(j=0; j<=m-1; j++)
+                        {
+                            vv = s.wtmp1[j];
+                            v = v+vv*vv;
+                        }
+                        s.dseweights[i] = v;
+                    }
+                    else
+                    {
+                        
+                        //
+                        // Logical variable, weight can be set to 1.0
+                        //
+                        s.dseweights[i] = 1.0;
+                    }
+                }
+                s.dsevalid = true;
+                return;
+            }
+            
+            //
+            // Compute weights from scratch
+            //
+            if( settings.pricing==0 )
+            {
+                for(i=0; i<=m-1; i++)
+                {
+                    s.dseweights[i] = 1.0;
+                }
+                s.dsevalid = true;
+                return;
+            }
+            alglib.ap.assert(false, "BasisRequestWeights: unexpected pricing type");
+        }
+
+
+        /*************************************************************************
+        This function updates triangular factorization by adding Q  to  basis  and
+        removing P from basis. It also updates index tables IsBasic[], BasicIdx[],
+        Basis.NIdx[].
+
+        AlphaQim contains intermediate result from Ftran for AlphaQ, it is used
+        by Forest-Tomlin update scheme. If other update is used, it is not referenced
+        at all.
+
+        X[], D[], Z are NOT recomputed.
+
+        Tau is used if Settings.Pricing=1, ignored otherwise.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void basisupdatetrf(dualsimplexbasis s,
+            sparse.sparsematrix at,
+            int p,
+            int q,
+            double[] alphaq,
+            double[] alphaqim,
+            int r,
+            double[] tau,
+            dualsimplexsettings settings,
+            alglib.xparams _params)
+        {
+            int m = 0;
+            int nn = 0;
+            int i = 0;
+            int j = 0;
+            bool processed = new bool();
+            double invaq = 0;
+            int dstoffs = 0;
+            int srcoffs = 0;
+            int srcidx = 0;
+            double srcval = 0;
+            double vcorner = 0;
+            int idxd = 0;
+            double v = 0;
+
+            m = s.m;
+            nn = s.ns;
+            
+            //
+            // Update index tables
+            //
+            // TODO: better code!!!!!!!!!!!!!!!!!!!!!!!
+            //
+            s.isbasic[p] = false;
+            s.isbasic[q] = true;
+            for(i=0; i<=m-1; i++)
+            {
+                if( s.idx[i]==p )
+                {
+                    s.idx[i] = q;
+                    break;
+                }
+            }
+            for(i=0; i<=nn-1; i++)
+            {
+                if( s.nidx[i]==q )
+                {
+                    s.nidx[i] = p;
+                    break;
+                }
+            }
+            
+            //
+            // Update dense factorization
+            //
+            if( ((s.trftype!=settings.trftype || s.trftype==0) || !s.isvalidtrf) || s.trfage>=settings.maxtrfage )
+            {
+                
+                //
+                // Complete refresh is needed for factorization
+                //
+                s.isvalidtrf = false;
+                basisfreshtrf(s, at, settings, _params);
+            }
+            else
+            {
+                processed = false;
+                if( (s.trftype==0 || s.trftype==1) || s.trftype==2 )
+                {
+                    
+                    //
+                    // Dense/sparse factorizations with dense PFI
+                    //
+                    alglib.ap.assert((double)(alphaq[r])!=(double)(0), "BasisUpdateTrf: integrity check failed, AlphaQ[R]=0");
+                    apserv.rvectorgrowto(ref s.densepfieta, (s.trfage+1)*m, _params);
+                    apserv.ivectorgrowto(ref s.rk, s.trfage+1, _params);
+                    s.rk[s.trfage] = r;
+                    invaq = 1.0/alphaq[r];
+                    for(i=0; i<=m-1; i++)
+                    {
+                        if( i!=r )
+                        {
+                            s.densepfieta[s.trfage*m+i] = -(alphaq[i]*invaq);
+                        }
+                        else
+                        {
+                            s.densepfieta[s.trfage*m+i] = invaq;
+                        }
+                    }
+                    apserv.inc(ref s.trfage, _params);
+                    processed = true;
+                }
+                if( s.trftype==3 )
+                {
+                    
+                    //
+                    // Sparse factorization with Forest-Tomlin update
+                    //
+                    alglib.ap.assert((double)(alphaq[r])!=(double)(0), "BasisUpdateTrf: integrity check failed, AlphaQ[R]=0");
+                    apserv.rvectorgrowto(ref s.densemu, (s.trfage+1)*m, _params);
+                    apserv.ivectorgrowto(ref s.rk, s.trfage+1, _params);
+                    apserv.ivectorgrowto(ref s.dk, s.trfage+1, _params);
+                    apserv.rvectorsetlengthatleast(ref s.utmp0, m, _params);
+                    
+                    //
+                    // Determine D - index of row being overwritten by Forest-Tomlin update
+                    //
+                    idxd = -1;
+                    for(i=0; i<=m-1; i++)
+                    {
+                        if( s.rowpermbwd[i]==r )
+                        {
+                            idxd = i;
+                            break;
+                        }
+                    }
+                    alglib.ap.assert(idxd>=0, "BasisUpdateTrf: unexpected integrity check failure");
+                    s.rk[s.trfage] = r;
+                    s.dk[s.trfage] = idxd;
+                    
+                    //
+                    // Modify L with permutation which moves D-th row/column to the end:
+                    // * rows 0...D-1 are left intact
+                    // * rows D+1...M-1 are moved one position up, with columns 0..D-1
+                    //   retained as is, and columns D+1...M-1 being moved one position left.
+                    // * last row is filled by permutation/modification of AlphaQim
+                    // Determine FT update coefficients in the process.
+                    //
+                    apserv.ivectorgrowto(ref s.sparsel.idx, s.sparsel.ridx[m]+m, _params);
+                    apserv.rvectorgrowto(ref s.sparsel.vals, s.sparsel.ridx[m]+m, _params);
+                    for(i=0; i<=m-1; i++)
+                    {
+                        s.utmp0[i] = 0;
+                    }
+                    for(i=idxd+1; i<=m-1; i++)
+                    {
+                        j = s.sparsel.ridx[i+1]-1;
+                        if( s.sparsel.idx[j]!=i || s.sparsel.vals[j]!=1 )
+                        {
+                            alglib.ap.assert(false, "UpdateTrf: integrity check failed for sparse L");
+                        }
+                        dstoffs = s.sparsel.ridx[i-1];
+                        srcoffs = s.sparsel.ridx[i];
+                        
+                        //
+                        // Read first element in the row (it has at least one - unit diagonal)
+                        //
+                        srcidx = s.sparsel.idx[srcoffs];
+                        srcval = s.sparsel.vals[srcoffs];
+                        
+                        //
+                        // Read/write columns 0...D-1
+                        //
+                        while( srcidx<idxd )
+                        {
+                            s.sparsel.idx[dstoffs] = srcidx;
+                            s.sparsel.vals[dstoffs] = srcval;
+                            dstoffs = dstoffs+1;
+                            srcoffs = srcoffs+1;
+                            srcidx = s.sparsel.idx[srcoffs];
+                            srcval = s.sparsel.vals[srcoffs];
+                        }
+                        
+                        //
+                        // If we have non-zero element in column D, use it as
+                        // right-hand side of intermediate linear system which
+                        // is used to determine coefficients of update matrix.
+                        //
+                        if( srcidx==idxd )
+                        {
+                            s.utmp0[i-1] = srcval;
+                            srcoffs = srcoffs+1;
+                            srcidx = s.sparsel.idx[srcoffs];
+                            srcval = s.sparsel.vals[srcoffs];
+                        }
+                        
+                        //
+                        // Process columns D+1...I-1
+                        //
+                        v = s.utmp0[i-1];
+                        while( srcidx<i )
+                        {
+                            s.sparsel.idx[dstoffs] = srcidx-1;
+                            s.sparsel.vals[dstoffs] = srcval;
+                            v = v-srcval*s.utmp0[srcidx-1];
+                            dstoffs = dstoffs+1;
+                            srcoffs = srcoffs+1;
+                            srcidx = s.sparsel.idx[srcoffs];
+                            srcval = s.sparsel.vals[srcoffs];
+                        }
+                        s.utmp0[i-1] = v;
+                        
+                        //
+                        // Write out unit diagonal, finalize row
+                        //
+                        s.sparsel.idx[dstoffs] = i-1;
+                        s.sparsel.vals[dstoffs] = 1;
+                        dstoffs = dstoffs+1;
+                        s.sparsel.ridx[i] = dstoffs;
+                    }
+                    s.utmp0[m-1] = 1;
+                    dstoffs = s.sparsel.ridx[m-1];
+                    for(j=0; j<=idxd-1; j++)
+                    {
+                        v = alphaqim[j];
+                        if( v!=0 )
+                        {
+                            s.sparsel.idx[dstoffs] = j;
+                            s.sparsel.vals[dstoffs] = v;
+                            dstoffs = dstoffs+1;
+                        }
+                    }
+                    vcorner = alphaqim[idxd];
+                    for(j=idxd+1; j<=m-1; j++)
+                    {
+                        v = alphaqim[j];
+                        if( v!=0 )
+                        {
+                            s.sparsel.idx[dstoffs] = j-1;
+                            s.sparsel.vals[dstoffs] = v;
+                            dstoffs = dstoffs+1;
+                            vcorner = vcorner-v*s.utmp0[j-1];
+                        }
+                    }
+                    s.sparsel.idx[dstoffs] = m-1;
+                    s.sparsel.vals[dstoffs] = 1;
+                    dstoffs = dstoffs+1;
+                    s.sparsel.ridx[m] = dstoffs;
+                    s.sparsel.ninitialized = s.sparsel.ridx[m];
+                    for(i=0; i<=m-1; i++)
+                    {
+                        j = s.sparsel.ridx[i+1];
+                        s.sparsel.didx[i] = j-1;
+                        s.sparsel.uidx[i] = j;
+                    }
+                    alglib.ap.assert(vcorner!=0, "UpdateTrf: corner element is zero, degeneracy detected");
+                    v = 1/vcorner;
+                    for(i=0; i<=m-2; i++)
+                    {
+                        s.densemu[s.trfage*m+i] = -(s.utmp0[i]*v);
+                    }
+                    s.densemu[s.trfage*m+m-1] = v;
+                    
+                    //
+                    // Multiply row permutation matrix by cyclic permutation applied to L
+                    //
+                    inversecyclicpermutation(s.rowpermbwd, m, idxd, ref s.utmpi, _params);
+                    
+                    //
+                    // Done
+                    //
+                    apserv.inc(ref s.trfage, _params);
+                    processed = true;
+                }
+                alglib.ap.assert(processed, "BasisUpdateTrf: unexpected TRF type");
+            }
+            
+            //
+            // Update pricing weights
+            //
+            alglib.ap.assert((settings.pricing==-1 || settings.pricing==0) || settings.pricing==1, "BasisUpdateTrf: unexpected Settings.Pricing");
+            processed = false;
+            if( settings.pricing==-1 )
+            {
+                
+                //
+                // Weights are recomputed from scratch at every step.
+                // VERY, VERY time consuming, used only for debug purposes.
+                //
+                s.dsevalid = false;
+                basisrequestweights(s, settings, _params);
+                processed = true;
+            }
+            if( settings.pricing==0 )
+            {
+                
+                //
+                // Weights are filled by 1.0
+                //
+                if( !s.dsevalid )
+                {
+                    for(i=0; i<=m-1; i++)
+                    {
+                        s.dseweights[i] = 1.0;
+                    }
+                    s.dsevalid = true;
+                }
+                processed = true;
+            }
+            if( settings.pricing==1 )
+            {
+                
+                //
+                // Weights are computed using DSE update formula.
+                //
+                if( s.dsevalid )
+                {
+                    
+                    //
+                    // Compute using update formula
+                    //
+                    for(i=0; i<=m-1; i++)
+                    {
+                        if( i!=r )
+                        {
+                            s.dseweights[i] = s.dseweights[i]-2*(alphaq[i]/alphaq[r])*tau[i]+s.dseweights[r]*math.sqr(alphaq[i]/alphaq[r]);
+                            s.dseweights[i] = Math.Max(s.dseweights[i], minbeta);
+                        }
+                    }
+                    s.dseweights[r] = s.dseweights[r]/(alphaq[r]*alphaq[r]);
+                }
+                else
+                {
+                    
+                    //
+                    // No prior values, compute from scratch (usually it is done only once)
+                    //
+                    basisrequestweights(s, settings, _params);
+                }
+                processed = true;
+            }
+            alglib.ap.assert(processed, "BasisUpdateTrf: unexpected pricing type");
+        }
+
+
+        /*************************************************************************
+        This function computes solution to B*x=r.
+
+        Output array is reallocated if needed. Temporary array TmpX[] is used  and
+        reallocated if necessary.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void basissolve(dualsimplexbasis s,
+            double[] r,
+            ref double[] x,
+            ref double[] tmpx,
+            alglib.xparams _params)
+        {
+            basissolvex(s, r, ref x, ref x, false, ref tmpx, _params);
+        }
+
+
+        /*************************************************************************
+        This function computes solution to B*x=r. It  also   additionally  outputs
+        intermediate  result  of multiplication by inv(DS)*inv(U)*inv(colPerm),  a
+        value essential for Forest-Tomlin update.
+
+        Output arrays are reallocated if needed. Temporary  array  TX[]   can   be
+        used/reallocated.
+
+        If NeedIntermediate is False or Forest-Tomlin updates are not used,
+        then Xim[] is not referenced at all.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void basissolvex(dualsimplexbasis s,
+            double[] r,
+            ref double[] x,
+            ref double[] xim,
+            bool needintermediate,
+            ref double[] tx,
+            alglib.xparams _params)
+        {
+            int m = 0;
+            int i = 0;
+            int d = 0;
+            int k = 0;
+            double v = 0;
+            double vd = 0;
+            double vv = 0;
+            bool processed = new bool();
+
+            alglib.ap.assert(s.isvalidtrf, "BasisSolve: integrity check failed");
+            m = s.m;
+            processed = false;
+            apserv.rvectorsetlengthatleast(ref tx, m, _params);
+            
+            //
+            // Dense/sparse factorizations with dense PFI
+            //
+            // NOTE: although we solve B*x=r, internally we store factorization of B^T
+            //
+            if( (s.trftype==0 || s.trftype==1) || s.trftype==2 )
+            {
+                alglib.ap.assert(s.trfage==0 || s.trftype!=0, "BasisSolve: integrity check failed TrfAge vs TrfType");
+                apserv.rvectorsetlengthatleast(ref x, m, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    x[i] = r[s.colpermbwd[i]];
+                }
+                if( s.trftype==0 || s.trftype==1 )
+                {
+                    
+                    //
+                    // Dense TRF
+                    //
+                    ablas.rmatrixtrsv(m, s.denselu, 0, 0, true, false, 1, x, 0, _params);
+                    ablas.rmatrixtrsv(m, s.denselu, 0, 0, false, true, 1, x, 0, _params);
+                }
+                else
+                {
+                    
+                    //
+                    // Sparse TRF
+                    //
+                    sparse.sparsetrsv(s.sparseu, true, false, 1, x, _params);
+                    sparse.sparsetrsv(s.sparsel, false, false, 1, x, _params);
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    tx[s.rowpermbwd[i]] = x[i];
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    x[i] = tx[i];
+                }
+                for(k=0; k<=s.trfage-1; k++)
+                {
+                    v = x[s.rk[k]];
+                    for(i=0; i<=m-1; i++)
+                    {
+                        x[i] = x[i]+s.densepfieta[k*m+i]*v;
+                    }
+                    x[s.rk[k]] = x[s.rk[k]]-v;
+                }
+                processed = true;
+            }
+            
+            //
+            // Sparse factorization with Forest-Tomlin update
+            //
+            // NOTE: although we solve B*x=r, internally we store factorization of B^T
+            //
+            if( s.trftype==3 )
+            {
+                apserv.rvectorsetlengthatleast(ref x, m, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    x[i] = r[s.colpermbwd[i]];
+                }
+                sparse.sparsetrsv(s.sparseu, true, false, 1, x, _params);
+                for(k=0; k<=s.trfage-1; k++)
+                {
+                    
+                    //
+                    // The code below is an amalgamation of two parts:
+                    //
+                    // cyclic permutation
+                    // V:=X[D];
+                    // for I:=D to M-2 do
+                    //     X[I]:=X[I+1];
+                    // X[M-1]:=V;
+                    // 
+                    // and triangular factor
+                    // V:=0;
+                    // for I:=D to M-1 do
+                    //     V:=V+X[I]*S.DenseMu[K*M+I];
+                    // X[M-1]:=V;
+                    //
+                    d = s.dk[k];
+                    vv = 0;
+                    vd = x[d];
+                    for(i=d; i<=m-2; i++)
+                    {
+                        v = x[i+1];
+                        x[i] = v;
+                        vv = vv+v*s.densemu[k*m+i];
+                    }
+                    x[m-1] = vv+vd*s.densemu[k*m+m-1];
+                }
+                if( needintermediate )
+                {
+                    apserv.rvectorsetlengthatleast(ref xim, m, _params);
+                    for(i=0; i<=m-1; i++)
+                    {
+                        xim[i] = x[i];
+                    }
+                }
+                sparse.sparsetrsv(s.sparsel, false, false, 1, x, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    tx[s.rowpermbwd[i]] = x[i];
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    x[i] = tx[i];
+                }
+                processed = true;
+            }
+            
+            //
+            // Integrity check
+            //
+            alglib.ap.assert(processed, "BasisSolve: unsupported TRF type");
+            v = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                v = v+x[i];
+            }
+            alglib.ap.assert(math.isfinite(v), "BasisSolve: integrity check failed (degeneracy in B?)");
+        }
+
+
+        /*************************************************************************
+        This function computes solution to (B^T)*x=r.
+
+        Output array is reallocated if needed. TX[] temporary is reallocated if
+        needed
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void basissolvet(dualsimplexbasis s,
+            double[] r,
+            ref double[] x,
+            ref double[] tx,
+            alglib.xparams _params)
+        {
+            int m = 0;
+            int i = 0;
+            int d = 0;
+            int k = 0;
+            double v = 0;
+            double vm = 0;
+            bool processed = new bool();
+
+            alglib.ap.assert(s.isvalidtrf, "BasisSolveT: integrity check failed");
+            m = s.m;
+            processed = false;
+            apserv.rvectorsetlengthatleast(ref tx, m, _params);
+            
+            //
+            // Dense factorizations
+            //
+            if( (s.trftype==0 || s.trftype==1) || s.trftype==2 )
+            {
+                alglib.ap.assert(s.trfage==0 || s.trftype!=0, "BasisSolveT: integrity check failed TrfAge vs TrfType");
+                apserv.rvectorsetlengthatleast(ref x, m, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    x[i] = r[i];
+                }
+                for(k=s.trfage-1; k>=0; k--)
+                {
+                    v = 0;
+                    for(i=0; i<=m-1; i++)
+                    {
+                        v = v+s.densepfieta[k*m+i]*x[i];
+                    }
+                    x[s.rk[k]] = v;
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    tx[i] = x[s.rowpermbwd[i]];
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    x[i] = tx[i];
+                }
+                if( s.trftype==0 || s.trftype==1 )
+                {
+                    
+                    //
+                    // Dense TRF
+                    //
+                    ablas.rmatrixtrsv(m, s.denselu, 0, 0, false, true, 0, x, 0, _params);
+                    ablas.rmatrixtrsv(m, s.denselu, 0, 0, true, false, 0, x, 0, _params);
+                }
+                else
+                {
+                    
+                    //
+                    // Sparse TRF
+                    //
+                    sparse.sparsetrsv(s.sparsel, false, false, 0, x, _params);
+                    sparse.sparsetrsv(s.sparseu, true, false, 0, x, _params);
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    tx[s.colpermbwd[i]] = x[i];
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    x[i] = tx[i];
+                }
+                processed = true;
+            }
+            
+            //
+            // Sparse factorization with Forest-Tomlin update
+            //
+            if( s.trftype==3 )
+            {
+                apserv.rvectorsetlengthatleast(ref x, m, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    x[i] = r[i];
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    tx[i] = x[s.rowpermbwd[i]];
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    x[i] = tx[i];
+                }
+                sparse.sparsetrsv(s.sparsel, false, false, 0, x, _params);
+                for(k=s.trfage-1; k>=0; k--)
+                {
+                    
+                    //
+                    // The code below is an amalgamation of two parts:
+                    //
+                    // triangular factor
+                    // V:=X[M-1];
+                    // for I:=D to M-2 do
+                    //     X[I]:=X[I]+S.DenseMu[K*M+I]*V;
+                    // X[M-1]:=S.DenseMu[K*M+(M-1)]*V;
+                    // 
+                    // inverse of cyclic permutation
+                    // V:=X[M-1];
+                    // for I:=M-1 downto D+1 do
+                    //     X[I]:=X[I-1];
+                    // X[D]:=V;
+                    //
+                    d = s.dk[k];
+                    vm = x[m-1];
+                    v = s.densemu[k*m+(m-1)]*vm;
+                    if( vm!=0 )
+                    {
+                        
+                        //
+                        // X[M-1] is non-zero, apply update
+                        //
+                        for(i=m-2; i>=d; i--)
+                        {
+                            x[i+1] = x[i]+s.densemu[k*m+i]*vm;
+                        }
+                    }
+                    else
+                    {
+                        
+                        //
+                        // X[M-1] is zero, just cyclic permutation
+                        //
+                        for(i=m-2; i>=d; i--)
+                        {
+                            x[i+1] = x[i];
+                        }
+                    }
+                    x[d] = v;
+                }
+                sparse.sparsetrsv(s.sparseut, false, false, 1, x, _params);
+                for(i=0; i<=m-1; i++)
+                {
+                    tx[s.colpermbwd[i]] = x[i];
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    x[i] = tx[i];
+                }
+                processed = true;
+            }
+            
+            //
+            // Integrity check
+            //
+            alglib.ap.assert(processed, "BasisSolveT: unsupported TRF type");
+            v = 0;
+            for(i=0; i<=m-1; i++)
+            {
+                v = v+x[i];
+            }
+            alglib.ap.assert(math.isfinite(v), "BasisSolveT: integrity check failed (degeneracy in B?)");
+        }
+
+
+        /*************************************************************************
+        This function computes product AN*XN, where AN is a  non-basic  subset  of
+        columns of A, and XN is a non-basic subset of columns of X.
+
+        Output array is reallocated if its size is too small.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void computeanxn(dualsimplexstate state,
+            dualsimplexsubproblem subproblem,
+            double[] x,
+            ref double[] y,
+            alglib.xparams _params)
+        {
+            int nn = 0;
+            int nx = 0;
+            int m = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int j0 = 0;
+            int j1 = 0;
+            double v = 0;
+
+            nx = subproblem.ns+subproblem.m;
+            m = subproblem.m;
+            nn = nx-m;
+            
+            //
+            // Integrity check
+            //
+            alglib.ap.assert(subproblem.state>=ssvalidxn, "ComputeANXN: XN is invalid");
+            
+            //
+            // Compute
+            //
+            apserv.rvectorsetlengthatleast(ref y, m, _params);
+            for(i=0; i<=m-1; i++)
+            {
+                y[i] = 0;
+            }
+            for(i=0; i<=nn-1; i++)
+            {
+                j0 = state.at.ridx[state.basis.nidx[i]];
+                j1 = state.at.ridx[state.basis.nidx[i]+1]-1;
+                v = x[state.basis.nidx[i]];
+                for(j=j0; j<=j1; j++)
+                {
+                    k = state.at.idx[j];
+                    y[k] = y[k]+v*state.at.vals[j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function computes product (AN^T)*y, where AN is a non-basic subset of
+        columns of A, and y is some vector.
+
+        Output array is set to full NX-sized length, with basic components of  the
+        output being set to zeros.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void computeantv(dualsimplexstate state,
+            double[] y,
+            ref double[] r,
+            alglib.xparams _params)
+        {
+            int nn = 0;
+            int nx = 0;
+            int m = 0;
+            int i = 0;
+            int j = 0;
+            int j0 = 0;
+            int j1 = 0;
+            double v = 0;
+
+            nx = state.ns+state.m;
+            m = state.m;
+            nn = nx-m;
+            
+            //
+            // Allocate output, set to zero
+            //
+            apserv.rvectorsetlengthatleast(ref r, nx, _params);
+            for(i=0; i<=nx-1; i++)
+            {
+                r[i] = 0;
+            }
+            for(i=0; i<=nn-1; i++)
+            {
+                j0 = state.at.ridx[state.basis.nidx[i]];
+                j1 = state.at.ridx[state.basis.nidx[i]+1]-1;
+                v = 0;
+                for(j=j0; j<=j1; j++)
+                {
+                    v = v+state.at.vals[j]*y[state.at.idx[j]];
+                }
+                r[state.basis.nidx[i]] = v;
+            }
+        }
+
+
+        /*************************************************************************
+        Returns True if I-th lower bound is present
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool hasbndl(dualsimplexsubproblem subproblem,
+            int i,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int k = 0;
+
+            k = subproblem.bndt[i];
+            result = false;
+            if( (k==0 || k==1) || k==3 )
+            {
+                result = true;
+                return result;
+            }
+            if( k==2 || k==4 )
+            {
+                result = false;
+                return result;
+            }
+            alglib.ap.assert(false, "HasBndL: integrity check failed");
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns True if I-th upper bound is present
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool hasbndu(dualsimplexsubproblem subproblem,
+            int i,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int k = 0;
+
+            k = subproblem.bndt[i];
+            result = false;
+            if( (k==0 || k==2) || k==3 )
+            {
+                result = true;
+                return result;
+            }
+            if( k==1 || k==4 )
+            {
+                result = false;
+                return result;
+            }
+            alglib.ap.assert(false, "HasBndL: integrity check failed");
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns True if I-th variable if free
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool isfree(dualsimplexsubproblem subproblem,
+            int i,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int k = 0;
+
+            k = subproblem.bndt[i];
+            result = false;
+            if( ((k==0 || k==1) || k==2) || k==3 )
+            {
+                result = false;
+                return result;
+            }
+            if( k==4 )
+            {
+                result = true;
+                return result;
+            }
+            alglib.ap.assert(false, "IsFree: integrity check failed");
+            return result;
+        }
+
+
+        /*************************************************************************
+        Downgrades problem state to the specified one (if status is lower than one
+        specified by user, nothing is changed)
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void downgradestate(dualsimplexsubproblem subproblem,
+            int s,
+            alglib.xparams _params)
+        {
+            subproblem.state = Math.Min(subproblem.state, s);
+        }
+
+
+        /*************************************************************************
+        Returns maximum dual infeasibility (only non-basic variables are  checked,
+        we assume that basic variables are good enough).
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static double dualfeasibilityerror(dualsimplexstate state,
+            dualsimplexsubproblem s,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int i = 0;
+            int j = 0;
+            int nn = 0;
+            int bndt = 0;
+
+            nn = s.ns;
+            alglib.ap.assert(s.state==ssvalid, "DualFeasibilityError: invalid X");
+            result = 0;
+            for(i=0; i<=nn-1; i++)
+            {
+                j = state.basis.nidx[i];
+                bndt = s.bndt[j];
+                if( bndt==ccfixed )
+                {
+                    continue;
+                }
+                if( bndt==ccrange )
+                {
+                    if( s.xa[j]==s.bndl[j] )
+                    {
+                        result = Math.Max(result, -s.d[j]);
+                        continue;
+                    }
+                    if( s.xa[j]==s.bndu[j] )
+                    {
+                        result = Math.Max(result, s.d[j]);
+                        continue;
+                    }
+                    alglib.ap.assert(false, "DualFeasibilityError: integrity check failed");
+                }
+                if( bndt==cclower )
+                {
+                    alglib.ap.assert(s.xa[j]==s.bndl[j], "DualFeasibilityError: integrity check failed");
+                    result = Math.Max(result, -s.d[j]);
+                    continue;
+                }
+                if( bndt==ccupper )
+                {
+                    alglib.ap.assert(s.xa[j]==s.bndu[j], "DualFeasibilityError: integrity check failed");
+                    result = Math.Max(result, s.d[j]);
+                    continue;
+                }
+                if( bndt==ccfree )
+                {
+                    result = Math.Max(result, Math.Abs(s.d[j]));
+                    continue;
+                }
+                alglib.ap.assert(false, "DSSOptimize: integrity check failed (infeasible constraint)");
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        Returns True for dual feasible basis (some minor dual feasibility error is
+        allowed), False otherwise
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool isdualfeasible(dualsimplexstate state,
+            dualsimplexsubproblem s,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+
+            result = (double)(dualfeasibilityerror(state, s, _params))<=(double)(dtol);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Transforms sequence of pivot permutations P0*P1*...*Pm to forward/backward
+        permutation representation.
+
+          -- ALGLIB --
+             Copyright 12.09.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void pivottobwd(int[] p,
+            int m,
+            ref int[] bwd,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+            int t = 0;
+
+            apserv.ivectorsetlengthatleast(ref bwd, m, _params);
+            for(i=0; i<=m-1; i++)
+            {
+                bwd[i] = i;
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                k = p[i];
+                if( k!=i )
+                {
+                    t = bwd[i];
+                    bwd[i] = bwd[k];
+                    bwd[k] = t;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Applies inverse cyclic permutation of [D,M-1) (element D is moved to the end, the
+        rest of elements is shifted one position backward) to the already existing
+        permutation.
+
+          -- ALGLIB --
+             Copyright 12.09.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void inversecyclicpermutation(int[] bwd,
+            int m,
+            int d,
+            ref int[] tmpi,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+
+            
+            //
+            // update Bwd[]
+            //
+            k = bwd[d];
+            for(i=d; i<=m-2; i++)
+            {
+                bwd[i] = bwd[i+1];
+            }
+            bwd[m-1] = k;
+        }
+
+
+        /*************************************************************************
+        Offloads basic components of X[], BndT[], BndL[], BndU[] to XB/BndTB/BndLB/BndUB.
+
+          -- ALGLIB --
+             Copyright 24.01.2019 by Bochkanov Sergey
+        *************************************************************************/
+        private static void offloadbasiccomponents(dualsimplexsubproblem s,
+            dualsimplexbasis basis,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int m = 0;
+
+            m = basis.m;
+            for(i=0; i<=m-1; i++)
+            {
+                s.xb[i] = s.xa[basis.idx[i]];
+                s.bndlb[i] = s.bndl[basis.idx[i]];
+                s.bndub[i] = s.bndu[basis.idx[i]];
+                s.bndtb[i] = s.bndt[basis.idx[i]];
+            }
+        }
+
+
+        /*************************************************************************
+        Recombines basic and non-basic components in X[]
+
+          -- ALGLIB --
+             Copyright 24.01.2019 by Bochkanov Sergey
+        *************************************************************************/
+        private static void recombinebasicnonbasicx(dualsimplexsubproblem s,
+            dualsimplexbasis basis,
+            alglib.xparams _params)
+        {
+            int m = 0;
+            int i = 0;
+
+            m = basis.m;
+            for(i=0; i<=m-1; i++)
+            {
+                s.xa[basis.idx[i]] = s.xb[i];
+            }
+        }
+
+
+        /*************************************************************************
+        Restores original scale of the variables, enforces box constraints (just
+        to be sure)
+
+          -- ALGLIB --
+             Copyright 24.01.2019 by Bochkanov Sergey
+        *************************************************************************/
+        private static void unscaleandenforce(double[] repx,
+            double[] repy,
+            double[] repdx,
+            dualsimplexstate s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int ns = 0;
+            int m = 0;
+
+            ns = s.ns;
+            m = s.m;
+            for(i=0; i<=ns-1; i++)
+            {
+                if( s.repstats[i]<0 )
+                {
+                    repx[i] = s.rawbndl[i];
+                    continue;
+                }
+                if( s.repstats[i]>0 )
+                {
+                    repx[i] = s.rawbndu[i];
+                    continue;
+                }
+                repx[i] = repx[i]*s.varscales[i];
+                if( math.isfinite(s.rawbndl[i]) )
+                {
+                    repx[i] = Math.Max(repx[i], s.rawbndl[i]);
+                }
+                if( math.isfinite(s.rawbndu[i]) )
+                {
+                    repx[i] = Math.Min(repx[i], s.rawbndu[i]);
+                }
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                repy[i] = repy[i]/s.rowscales[i];
+                repdx[i] = repdx[i]/s.rowscales[i];
+            }
+        }
+
+
+    }
+    public class minlp
+    {
+        /*************************************************************************
+        This object stores linear solver state.
+        You should use functions provided by MinLP subpackage to work with this
+        object
+        *************************************************************************/
+        public class minlpstate : apobject
+        {
+            public int n;
+            public int algokind;
+            public double[] s;
+            public double[] c;
+            public double[] bndl;
+            public double[] bndu;
+            public int m;
+            public sparse.sparsematrix a;
+            public double[] al;
+            public double[] au;
+            public double[] xs;
+            public double[] ys;
+            public int[] cs;
+            public double repf;
+            public double repprimalerror;
+            public double repdualerror;
+            public int repiterationscount;
+            public int repterminationtype;
+            public reviseddualsimplex.dualsimplexstate dss;
+            public int[] adddtmpi;
+            public double[] adddtmpr;
+            public minlpstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                s = new double[0];
+                c = new double[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                a = new sparse.sparsematrix();
+                al = new double[0];
+                au = new double[0];
+                xs = new double[0];
+                ys = new double[0];
+                cs = new int[0];
+                dss = new reviseddualsimplex.dualsimplexstate();
+                adddtmpi = new int[0];
+                adddtmpr = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                minlpstate _result = new minlpstate();
+                _result.n = n;
+                _result.algokind = algokind;
+                _result.s = (double[])s.Clone();
+                _result.c = (double[])c.Clone();
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.m = m;
+                _result.a = (sparse.sparsematrix)a.make_copy();
+                _result.al = (double[])al.Clone();
+                _result.au = (double[])au.Clone();
+                _result.xs = (double[])xs.Clone();
+                _result.ys = (double[])ys.Clone();
+                _result.cs = (int[])cs.Clone();
+                _result.repf = repf;
+                _result.repprimalerror = repprimalerror;
+                _result.repdualerror = repdualerror;
+                _result.repiterationscount = repiterationscount;
+                _result.repterminationtype = repterminationtype;
+                _result.dss = (reviseddualsimplex.dualsimplexstate)dss.make_copy();
+                _result.adddtmpi = (int[])adddtmpi.Clone();
+                _result.adddtmpr = (double[])adddtmpr.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure stores optimization report:
+        * f                         target function value
+        * y                         dual variables
+        * stats                     array[N+M], statuses of box (N) and linear (M)
+                                    constraints:
+                                    * stats[i]>0  =>  constraint at upper bound
+                                                      (also used for free non-basic
+                                                      variables set to zero)
+                                    * stats[i]<0  =>  constraint at lower bound
+                                    * stats[i]=0  =>  constraint is inactive, basic
+                                                      variable
+        * primalerror               primal feasibility error
+        * dualerror                 dual feasibility error
+        * iterationscount           iteration count
+        * terminationtype           completion code (see below)
+
+        Completion codes:
+        * -4    LP problem is primal unbounded (dual infeasible)
+        * -3    LP problem is primal infeasible (dual unbounded)
+        *  1..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 minlpreport : apobject
+        {
+            public double f;
+            public double[] y;
+            public int[] stats;
+            public double primalerror;
+            public double dualerror;
+            public int iterationscount;
+            public int terminationtype;
+            public minlpreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+                y = new double[0];
+                stats = new int[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                minlpreport _result = new minlpreport();
+                _result.f = f;
+                _result.y = (double[])y.Clone();
+                _result.stats = (int[])stats.Clone();
+                _result.primalerror = primalerror;
+                _result.dualerror = dualerror;
+                _result.iterationscount = iterationscount;
+                _result.terminationtype = terminationtype;
+                return _result;
+            }
+        };
+
+
+
+
+        public const int alllogicalsbasis = 0;
+
+
+        /*************************************************************************
+                                    LINEAR PROGRAMMING
+
+        The subroutine creates LP  solver.  After  initial  creation  it  contains
+        default optimization problem with zero cost vector and all variables being
+        fixed to zero values and no constraints.
+
+        In order to actually solve something you should:
+        * set cost vector with minlpsetcost()
+        * set variable bounds with minlpsetbc() or minlpsetbcall()
+        * specify constraint matrix with one of the following functions:
+          [*] minlpsetlc()        for dense one-sided constraints
+          [*] minlpsetlc2dense()  for dense two-sided constraints
+          [*] minlpsetlc2()       for sparse two-sided constraints
+          [*] minlpaddlc2dense()  to add one dense row to constraint matrix
+          [*] minlpaddlc2()       to add one row to constraint matrix (compressed format)
+        * call minlpoptimize() to run the solver and  minlpresults()  to  get  the
+          solution vector and additional information.
+
+        Presently  this  optimizer  supports  only  revised  simplex   method   as
+        underlying solver. DSE pricing and bounds flipping ratio  test  (aka  long
+        dual step) are supported. Large-scale sparse LU solver with  Forest-Tomlin
+        is used internally as linear algebra driver.
+
+        Future releases of ALGLIB may introduce other solvers.
+
+        INPUT PARAMETERS:
+            N       -   problem size
+            
+        OUTPUT PARAMETERS:
+            State   -   optimizer in the default state
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlpcreate(int n,
+            minlpstate state,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(n>=1, "MinLPCreate: N<1");
+            
+            //
+            // Initialize
+            //
+            state.n = n;
+            state.m = 0;
+            state.algokind = 1;
+            state.c = new double[n];
+            state.s = new double[n];
+            state.bndl = new double[n];
+            state.bndu = new double[n];
+            state.xs = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                state.bndl[i] = 0;
+                state.bndu[i] = 0;
+                state.c[i] = 0.0;
+                state.s[i] = 1.0;
+                state.xs[i] = 1.0;
+            }
+            clearreportfields(state, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets cost term for LP solver.
+
+        By default, cost term is zero.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            C       -   cost term, array[N].
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlpsetcost(minlpstate state,
+            double[] c,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+
+            n = state.n;
+            alglib.ap.assert(alglib.ap.len(c)>=n, "MinLPSetCost: Length(C)<N");
+            alglib.ap.assert(apserv.isfinitevector(c, n, _params), "MinLPSetCost: C contains infinite or NaN elements");
+            for(i=0; i<=n-1; i++)
+            {
+                state.c[i] = c[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets scaling coefficients.
+
+        ALGLIB optimizers use scaling matrices to test stopping  conditions and as
+        preconditioner.
+
+        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
+
+        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 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlpsetscale(minlpstate state,
+            double[] s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(s)>=state.n, "MinLPSetScale: Length(S)<N");
+            for(i=0; i<=state.n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(s[i]), "MinLPSetScale: S contains infinite or NAN elements");
+                alglib.ap.assert((double)(s[i])!=(double)(0), "MinLPSetScale: S contains zero elements");
+            }
+            for(i=0; i<=state.n-1; i++)
+            {
+                state.s[i] = Math.Abs(s[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets box constraints for LP solver (all variables  at  once,
+        different constraints for different variables).
+
+        The default state of constraints is to have all variables fixed  at  zero.
+        You have to overwrite it by your own constraint vector. Constraint  status
+        is preserved until constraints are  explicitly  overwritten  with  another
+        minlpsetbc()  call,   overwritten   with  minlpsetbcall(),  or   partially
+        overwritten with minlmsetbci() call.
+
+        Following types of constraints are supported:
+
+            DESCRIPTION         CONSTRAINT              HOW TO SPECIFY
+            fixed variable      x[i]=Bnd[i]             BndL[i]=BndU[i]
+            lower bound         BndL[i]<=x[i]           BndU[i]=+INF
+            upper bound         x[i]<=BndU[i]           BndL[i]=-INF
+            range               BndL[i]<=x[i]<=BndU[i]  ...
+            free variable       -                       BndL[I]=-INF, BndU[I]+INF
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            BndL    -   lower bounds, array[N].
+            BndU    -   upper bounds, array[N].
+
+        NOTE: infinite values can be specified by means of Double.PositiveInfinity
+              and  Double.NegativeInfinity  (in  C#)  and  alglib::fp_posinf   and
+              alglib::fp_neginf (in C++).
+              
+        NOTE: you may replace infinities by very small/very large values,  but  it
+              is not recommended because large numbers may introduce large numerical
+              errors in the algorithm.
+              
+        NOTE: if constraints for all variables are same you may use minlpsetbcall()
+              which allows to specify constraints without using arrays.
+              
+        NOTE: BndL>BndU will result in LP problem being recognized as infeasible.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlpsetbc(minlpstate state,
+            double[] bndl,
+            double[] bndu,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = state.n;
+            alglib.ap.assert(alglib.ap.len(bndl)>=n, "MinLPSetBC: Length(BndL)<N");
+            alglib.ap.assert(alglib.ap.len(bndu)>=n, "MinLPSetBC: Length(BndU)<N");
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(bndl[i]) || Double.IsNegativeInfinity(bndl[i]), "MinLPSetBC: BndL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(bndu[i]) || Double.IsPositiveInfinity(bndu[i]), "MinLPSetBC: BndU contains NAN or -INF");
+                state.bndl[i] = bndl[i];
+                state.bndu[i] = bndu[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets box constraints for LP solver (all variables  at  once,
+        same constraints for all variables)
+
+        The default state of constraints is to have all variables fixed  at  zero.
+        You have to overwrite it by your own constraint vector. Constraint  status
+        is preserved until constraints are  explicitly  overwritten  with  another
+        minlpsetbc() call or partially overwritten with minlpsetbcall().
+
+        Following types of constraints are supported:
+
+            DESCRIPTION         CONSTRAINT              HOW TO SPECIFY
+            fixed variable      x[i]=Bnd[i]             BndL[i]=BndU[i]
+            lower bound         BndL[i]<=x[i]           BndU[i]=+INF
+            upper bound         x[i]<=BndU[i]           BndL[i]=-INF
+            range               BndL[i]<=x[i]<=BndU[i]  ...
+            free variable       -                       BndL[I]=-INF, BndU[I]+INF
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            BndL    -   lower bound, same for all variables
+            BndU    -   upper bound, same for all variables
+
+        NOTE: infinite values can be specified by means of Double.PositiveInfinity
+              and  Double.NegativeInfinity  (in  C#)  and  alglib::fp_posinf   and
+              alglib::fp_neginf (in C++).
+              
+        NOTE: you may replace infinities by very small/very large values,  but  it
+              is not recommended because large numbers may introduce large numerical
+              errors in the algorithm.
+              
+        NOTE: minlpsetbc() can  be  used  to  specify  different  constraints  for
+              different variables.
+              
+        NOTE: BndL>BndU will result in LP problem being recognized as infeasible.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlpsetbcall(minlpstate state,
+            double bndl,
+            double bndu,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = state.n;
+            alglib.ap.assert(math.isfinite(bndl) || Double.IsNegativeInfinity(bndl), "MinLPSetBC: BndL is NAN or +INF");
+            alglib.ap.assert(math.isfinite(bndu) || Double.IsPositiveInfinity(bndu), "MinLPSetBC: BndU is NAN or -INF");
+            for(i=0; i<=n-1; i++)
+            {
+                state.bndl[i] = bndl;
+                state.bndu[i] = bndu;
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets box constraints for I-th variable (other variables are
+        not modified).
+
+        The default state of constraints is to have all variables fixed  at  zero.
+        You have to overwrite it by your own constraint vector.
+
+        Following types of constraints are supported:
+
+            DESCRIPTION         CONSTRAINT              HOW TO SPECIFY
+            fixed variable      x[i]=Bnd[i]             BndL[i]=BndU[i]
+            lower bound         BndL[i]<=x[i]           BndU[i]=+INF
+            upper bound         x[i]<=BndU[i]           BndL[i]=-INF
+            range               BndL[i]<=x[i]<=BndU[i]  ...
+            free variable       -                       BndL[I]=-INF, BndU[I]+INF
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            I       -   variable index, in [0,N)
+            BndL    -   lower bound for I-th variable
+            BndU    -   upper bound for I-th variable
+
+        NOTE: infinite values can be specified by means of Double.PositiveInfinity
+              and  Double.NegativeInfinity  (in  C#)  and  alglib::fp_posinf   and
+              alglib::fp_neginf (in C++).
+              
+        NOTE: you may replace infinities by very small/very large values,  but  it
+              is not recommended because large numbers may introduce large numerical
+              errors in the algorithm.
+              
+        NOTE: minlpsetbc() can  be  used  to  specify  different  constraints  for
+              different variables.
+              
+        NOTE: BndL>BndU will result in LP problem being recognized as infeasible.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlpsetbci(minlpstate state,
+            int i,
+            double bndl,
+            double bndu,
+            alglib.xparams _params)
+        {
+            int n = 0;
+
+            n = state.n;
+            alglib.ap.assert(i>=0 && i<n, "MinLPSetBCi: I is outside of [0,N)");
+            alglib.ap.assert(math.isfinite(bndl) || Double.IsNegativeInfinity(bndl), "MinLPSetBCi: BndL is NAN or +INF");
+            alglib.ap.assert(math.isfinite(bndu) || Double.IsPositiveInfinity(bndu), "MinLPSetBCi: BndU is NAN or -INF");
+            state.bndl[i] = bndl;
+            state.bndu[i] = bndu;
+        }
+
+
+        /*************************************************************************
+        This function sets one-sided linear constraints A*x ~ AU, where "~" can be
+        a mix of "<=", "=" and ">=".
+
+        IMPORTANT: this function is provided here for compatibility with the  rest
+                   of ALGLIB optimizers which accept constraints  in  format  like
+                   this one. Many real-life problems feature two-sided constraints
+                   like a0 <= a*x <= a1. It is really inefficient to add them as a
+                   pair of one-sided constraints.
+                   
+                   Use minlpsetlc2dense(), minlpsetlc2(), minlpaddlc2()  (or   its
+                   sparse version) wherever possible.
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with minlpcreate() call.
+            A       -   linear constraints, array[K,N+1]. Each row of A represents
+                        one constraint, with first N elements being linear coefficients,
+                        and last element being right side.
+            CT      -   constraint types, array[K]:
+                        * if CT[i]>0, then I-th constraint is A[i,*]*x >= A[i,n]
+                        * if CT[i]=0, then I-th constraint is A[i,*]*x  = A[i,n]
+                        * if CT[i]<0, then I-th constraint is A[i,*]*x <= A[i,n]
+            K       -   number of equality/inequality constraints,  K>=0;  if  not
+                        given, inferred from sizes of A and CT.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlpsetlc(minlpstate state,
+            double[,] a,
+            int[] ct,
+            int k,
+            alglib.xparams _params)
+        {
+            double[] al = new double[0];
+            double[] au = new double[0];
+            int n = 0;
+            int i = 0;
+
+            n = state.n;
+            alglib.ap.assert(k>=0, "MinLPSetLC: K<0");
+            alglib.ap.assert(k==0 || alglib.ap.cols(a)>=n+1, "MinLPSetLC: Cols(A)<N+1");
+            alglib.ap.assert(alglib.ap.rows(a)>=k, "MinLPSetLC: Rows(A)<K");
+            alglib.ap.assert(alglib.ap.len(ct)>=k, "MinLPSetLC: Length(CT)<K");
+            alglib.ap.assert(apserv.apservisfinitematrix(a, k, n+1, _params), "MinLPSetLC: A contains infinite or NaN values!");
+            
+            //
+            // Handle zero K
+            //
+            if( k==0 )
+            {
+                state.m = 0;
+                return;
+            }
+            
+            //
+            // Convert constraints to two-sided storage format, call another function
+            //
+            al = new double[k];
+            au = new double[k];
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]>0 )
+                {
+                    al[i] = a[i,n];
+                    au[i] = Double.PositiveInfinity;
+                    continue;
+                }
+                if( ct[i]<0 )
+                {
+                    al[i] = Double.NegativeInfinity;
+                    au[i] = a[i,n];
+                    continue;
+                }
+                al[i] = a[i,n];
+                au[i] = a[i,n];
+            }
+            minlpsetlc2dense(state, a, al, au, k, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets two-sided linear constraints AL <= A*x <= AU.
+
+        This version accepts dense matrix as  input;  internally  LP  solver  uses
+        sparse storage  anyway  (most  LP  problems  are  sparse),  but  for  your
+        convenience it may accept dense inputs. This  function  overwrites  linear
+        constraints set by previous calls (if such calls were made).
+
+        We recommend you to use sparse version of this function unless  you  solve
+        small-scale LP problem (less than few hundreds of variables).
+
+        NOTE: there also exist several versions of this function:
+              * one-sided dense version which  accepts  constraints  in  the  same
+                format as one used by QP and  NLP solvers
+              * two-sided sparse version which accepts sparse matrix
+              * two-sided dense  version which allows you to add constraints row by row
+              * two-sided sparse version which allows you to add constraints row by row
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with minlpcreate() call.
+            A       -   linear constraints, array[K,N]. Each row of  A  represents
+                        one  constraint. One-sided  inequality   constraints, two-
+                        sided inequality  constraints,  equality  constraints  are
+                        supported (see below)
+            AL, AU  -   lower and upper bounds, array[K];
+                        * AL[i]=AU[i] => equality constraint Ai*x
+                        * AL[i]<AU[i] => two-sided constraint AL[i]<=Ai*x<=AU[i]
+                        * AL[i]=-INF  => one-sided constraint Ai*x<=AU[i]
+                        * AU[i]=+INF  => one-sided constraint AL[i]<=Ai*x
+                        * AL[i]=-INF, AU[i]=+INF => constraint is ignored
+            K       -   number of equality/inequality constraints,  K>=0;  if  not
+                        given, inferred from sizes of A, AL, AU.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlpsetlc2dense(minlpstate state,
+            double[,] a,
+            double[] al,
+            double[] au,
+            int k,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int n = 0;
+            int nz = 0;
+            int[] nrs = new int[0];
+
+            n = state.n;
+            alglib.ap.assert(k>=0, "MinLPSetLC2Dense: K<0");
+            alglib.ap.assert(k==0 || alglib.ap.cols(a)>=n, "MinLPSetLC2Dense: Cols(A)<N");
+            alglib.ap.assert(alglib.ap.rows(a)>=k, "MinLPSetLC2Dense: Rows(A)<K");
+            alglib.ap.assert(alglib.ap.len(al)>=k, "MinLPSetLC2Dense: Length(AL)<K");
+            alglib.ap.assert(alglib.ap.len(au)>=k, "MinLPSetLC2Dense: Length(AU)<K");
+            alglib.ap.assert(apserv.apservisfinitematrix(a, k, n, _params), "MinLPSetLC2Dense: A contains infinite or NaN values!");
+            
+            //
+            // Count actual (different from -INF<=A*x<=+INF) constraints;
+            // count non-zero elements in each row.
+            //
+            nrs = new int[k];
+            state.m = k;
+            if( state.m==0 )
+            {
+                return;
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(al[i]) || Double.IsNegativeInfinity(al[i]), "MinLPSetLC2Dense: AL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(au[i]) || Double.IsPositiveInfinity(au[i]), "MinLPSetLC2Dense: AU contains NAN or -INF");
+                nz = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    if( (double)(a[i,j])!=(double)(0) )
+                    {
+                        apserv.inc(ref nz, _params);
+                    }
+                }
+                nrs[i] = nz;
+            }
+            
+            //
+            // Allocate storage, copy
+            //
+            apserv.rvectorsetlengthatleast(ref state.al, state.m, _params);
+            apserv.rvectorsetlengthatleast(ref state.au, state.m, _params);
+            sparse.sparsecreatecrsbuf(state.m, n, nrs, state.a, _params);
+            for(i=0; i<=k-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( (double)(a[i,j])!=(double)(0) )
+                    {
+                        sparse.sparseset(state.a, i, j, a[i,j], _params);
+                    }
+                }
+                state.al[i] = al[i];
+                state.au[i] = au[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This  function  sets  two-sided linear  constraints  AL <= A*x <= AU  with
+        sparse constraining matrix A. Recommended for large-scale problems.
+
+        This  function  overwrites  linear  (non-box)  constraints set by previous
+        calls (if such calls were made).
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with minlpcreate() call.
+            A       -   sparse matrix with size [K,N] (exactly!).
+                        Each row of A represents one general linear constraint.
+                        A can be stored in any sparse storage format.
+            AL, AU  -   lower and upper bounds, array[K];
+                        * AL[i]=AU[i] => equality constraint Ai*x
+                        * AL[i]<AU[i] => two-sided constraint AL[i]<=Ai*x<=AU[i]
+                        * AL[i]=-INF  => one-sided constraint Ai*x<=AU[i]
+                        * AU[i]=+INF  => one-sided constraint AL[i]<=Ai*x
+                        * AL[i]=-INF, AU[i]=+INF => constraint is ignored
+            K       -   number  of equality/inequality constraints, K>=0.  If  K=0
+                        is specified, A, AL, AU are ignored.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlpsetlc2(minlpstate state,
+            sparse.sparsematrix a,
+            double[] al,
+            double[] au,
+            int k,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+
+            n = state.n;
+            
+            //
+            // Quick exit
+            //
+            if( k==0 )
+            {
+                state.m = 0;
+                return;
+            }
+            
+            //
+            // Integrity checks
+            //
+            alglib.ap.assert(k>0, "MinLPSetLC2: K<0");
+            alglib.ap.assert(sparse.sparsegetncols(a, _params)==n, "MinLPSetLC2: Cols(A)<>N");
+            alglib.ap.assert(sparse.sparsegetnrows(a, _params)==k, "MinLPSetLC2: Rows(A)<>K");
+            alglib.ap.assert(alglib.ap.len(al)>=k, "MinLPSetLC2: Length(AL)<K");
+            alglib.ap.assert(alglib.ap.len(au)>=k, "MinLPSetLC2: Length(AU)<K");
+            for(i=0; i<=k-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(al[i]) || Double.IsNegativeInfinity(al[i]), "MinLPSetLC2: AL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(au[i]) || Double.IsPositiveInfinity(au[i]), "MinLPSetLC2: AU contains NAN or -INF");
+            }
+            
+            //
+            // Copy
+            //
+            state.m = k;
+            sparse.sparsecopytocrsbuf(a, state.a, _params);
+            apserv.rvectorsetlengthatleast(ref state.al, k, _params);
+            apserv.rvectorsetlengthatleast(ref state.au, k, _params);
+            for(i=0; i<=k-1; i++)
+            {
+                state.al[i] = al[i];
+                state.au[i] = au[i];
+            }
+        }
+
+
+        /*************************************************************************
+        This function appends two-sided linear constraint  AL <= A*x <= AU  to the
+        list of currently present constraints.
+
+        This version accepts dense constraint vector as input, but  sparsifies  it
+        for internal storage and processing. Thus, time to add one  constraint  in
+        is O(N) - we have to scan entire array of length N. Sparse version of this
+        function is order of magnitude faster for  constraints  with  just  a  few
+        nonzeros per row.
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with minlpcreate() call.
+            A       -   linear constraint coefficient, array[N], right side is NOT
+                        included.
+            AL, AU  -   lower and upper bounds;
+                        * AL=AU    => equality constraint Ai*x
+                        * AL<AU    => two-sided constraint AL<=A*x<=AU
+                        * AL=-INF  => one-sided constraint Ai*x<=AU
+                        * AU=+INF  => one-sided constraint AL<=Ai*x
+                        * AL=-INF, AU=+INF => constraint is ignored
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlpaddlc2dense(minlpstate state,
+            double[] a,
+            double al,
+            double au,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+            int nnz = 0;
+
+            n = state.n;
+            alglib.ap.assert(alglib.ap.len(a)>=n, "MinLPAddLC2Dense: Length(A)<N");
+            alglib.ap.assert(apserv.isfinitevector(a, n, _params), "MinLPAddLC2Dense: A contains infinite or NaN values!");
+            alglib.ap.assert(math.isfinite(al) || Double.IsNegativeInfinity(al), "MinLPAddLC2Dense: AL is NAN or +INF");
+            alglib.ap.assert(math.isfinite(au) || Double.IsPositiveInfinity(au), "MinLPAddLC2Dense: AU is NAN or -INF");
+            apserv.ivectorsetlengthatleast(ref state.adddtmpi, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.adddtmpr, n, _params);
+            nnz = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                if( a[i]!=0.0 )
+                {
+                    state.adddtmpi[nnz] = i;
+                    state.adddtmpr[nnz] = a[i];
+                    nnz = nnz+1;
+                }
+            }
+            minlpaddlc2(state, state.adddtmpi, state.adddtmpr, nnz, al, au, _params);
+        }
+
+
+        /*************************************************************************
+        This function appends two-sided linear constraint  AL <= A*x <= AU  to the
+        list of currently present constraints.
+
+        Constraint is passed in compressed format: as list of non-zero entries  of
+        coefficient vector A. Such approach is more efficient than  dense  storage
+        for highly sparse constraint vectors.
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with minlpcreate() call.
+            IdxA    -   array[NNZ], indexes of non-zero elements of A:
+                        * can be unsorted
+                        * can include duplicate indexes (corresponding entries  of
+                          ValA[] will be summed)
+            ValA    -   array[NNZ], values of non-zero elements of A
+            NNZ     -   number of non-zero coefficients in A
+            AL, AU  -   lower and upper bounds;
+                        * AL=AU    => equality constraint A*x
+                        * AL<AU    => two-sided constraint AL<=A*x<=AU
+                        * AL=-INF  => one-sided constraint A*x<=AU
+                        * AU=+INF  => one-sided constraint AL<=A*x
+                        * AL=-INF, AU=+INF => constraint is ignored
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlpaddlc2(minlpstate state,
+            int[] idxa,
+            double[] vala,
+            int nnz,
+            double al,
+            double au,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int offs = 0;
+            int offsdst = 0;
+            int m = 0;
+            int n = 0;
+            int didx = 0;
+            int uidx = 0;
+
+            m = state.m;
+            n = state.n;
+            
+            //
+            // Check inputs
+            //
+            alglib.ap.assert(nnz>=0, "MinLPAddLC2: NNZ<0");
+            alglib.ap.assert(alglib.ap.len(idxa)>=nnz, "MinLPAddLC2: Length(IdxA)<NNZ");
+            alglib.ap.assert(alglib.ap.len(vala)>=nnz, "MinLPAddLC2: Length(ValA)<NNZ");
+            for(i=0; i<=nnz-1; i++)
+            {
+                alglib.ap.assert(idxa[i]>=0 && idxa[i]<n, "MinLPAddLC2: IdxA contains indexes outside of [0,N) range");
+            }
+            alglib.ap.assert(apserv.isfinitevector(vala, nnz, _params), "MinLPAddLC2: ValA contains infinite or NaN values!");
+            alglib.ap.assert(math.isfinite(al) || Double.IsNegativeInfinity(al), "MinLPAddLC2Dense: AL is NAN or +INF");
+            alglib.ap.assert(math.isfinite(au) || Double.IsPositiveInfinity(au), "MinLPAddLC2Dense: AU is NAN or -INF");
+            
+            //
+            // If M=0, it means that A is uninitialized.
+            // Prepare sparse matrix structure
+            //
+            if( m==0 )
+            {
+                state.a.matrixtype = 1;
+                state.a.m = 0;
+                state.a.n = n;
+                state.a.ninitialized = 0;
+                apserv.ivectorsetlengthatleast(ref state.a.ridx, 1, _params);
+                state.a.ridx[0] = 0;
+            }
+            
+            //
+            // Reallocate storage
+            //
+            offs = state.a.ridx[m];
+            apserv.ivectorgrowto(ref state.a.idx, offs+nnz, _params);
+            apserv.rvectorgrowto(ref state.a.vals, offs+nnz, _params);
+            apserv.ivectorgrowto(ref state.a.didx, m+1, _params);
+            apserv.ivectorgrowto(ref state.a.uidx, m+1, _params);
+            apserv.ivectorgrowto(ref state.a.ridx, m+2, _params);
+            apserv.rvectorgrowto(ref state.al, m+1, _params);
+            apserv.rvectorgrowto(ref state.au, m+1, _params);
+            
+            //
+            // If NNZ=0, perform quick and simple row append. 
+            //
+            if( nnz==0 )
+            {
+                state.a.didx[m] = state.a.ridx[m];
+                state.a.uidx[m] = state.a.ridx[m];
+                state.a.ridx[m+1] = state.a.ridx[m];
+                state.al[m] = al;
+                state.au[m] = au;
+                state.a.m = m+1;
+                state.m = m+1;
+                return;
+            }
+            
+            //
+            // Now we are sure that A contains properly initialized sparse
+            // matrix (or some appropriate dummy for M=0) and we have NNZ>0
+            // (no need to care about degenerate cases).
+            //
+            // Append rows to A:
+            // * append data
+            // * sort in place
+            // * merge duplicate indexes
+            // * compute DIdx and UIdx
+            //
+            //
+            for(i=0; i<=nnz-1; i++)
+            {
+                state.a.idx[offs+i] = idxa[i];
+                state.a.vals[offs+i] = vala[i];
+            }
+            tsort.tagsortmiddleir(ref state.a.idx, ref state.a.vals, offs, nnz, _params);
+            offsdst = offs;
+            for(i=1; i<=nnz-1; i++)
+            {
+                if( state.a.idx[offsdst]!=state.a.idx[offs+i] )
+                {
+                    offsdst = offsdst+1;
+                    state.a.idx[offsdst] = state.a.idx[offs+i];
+                    state.a.vals[offsdst] = state.a.vals[offs+i];
+                }
+                else
+                {
+                    state.a.vals[offsdst] = state.a.vals[offsdst]+state.a.vals[offs+i];
+                }
+            }
+            nnz = offsdst-offs+1;
+            uidx = -1;
+            didx = -1;
+            for(j=offs; j<=offsdst; j++)
+            {
+                k = state.a.idx[j];
+                if( k==m )
+                {
+                    didx = j;
+                }
+                else
+                {
+                    if( k>m && uidx==-1 )
+                    {
+                        uidx = j;
+                        break;
+                    }
+                }
+            }
+            if( uidx==-1 )
+            {
+                uidx = offsdst+1;
+            }
+            if( didx==-1 )
+            {
+                didx = uidx;
+            }
+            state.a.didx[m] = didx;
+            state.a.uidx[m] = uidx;
+            state.a.ridx[m+1] = offsdst+1;
+            state.a.m = m+1;
+            state.al[m] = al;
+            state.au[m] = au;
+            state.m = m+1;
+        }
+
+
+        /*************************************************************************
+        This function solves LP problem.
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        You should use minlpresults() function to access results  after  calls  to
+        this function.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey.
+        *************************************************************************/
+        public static void minlpoptimize(minlpstate state,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int m = 0;
+            int i = 0;
+            reviseddualsimplex.dualsimplexsettings settings = new reviseddualsimplex.dualsimplexsettings();
+            double[,] dummy = new double[0,0];
+            reviseddualsimplex.dualsimplexbasis dummybasis = new reviseddualsimplex.dualsimplexbasis();
+
+            n = state.n;
+            m = state.m;
+            clearreportfields(state, _params);
+            
+            //
+            // Most basic check for correctness of constraints
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(state.bndl[i])>(double)(state.bndu[i]) )
+                {
+                    state.repterminationtype = -3;
+                    return;
+                }
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                if( (double)(state.al[i])>(double)(state.au[i]) )
+                {
+                    state.repterminationtype = -3;
+                    return;
+                }
+            }
+            
+            //
+            // Call current solver
+            //
+            if( state.algokind==1 )
+            {
+                
+                //
+                // Dual simplex method
+                //
+                reviseddualsimplex.dsssettingsinit(settings, _params);
+                reviseddualsimplex.dssinit(n, state.dss, _params);
+                reviseddualsimplex.dsssetproblem(state.dss, state.c, state.bndl, state.bndu, state.s, dummy, state.a, 1, state.al, state.au, m, dummybasis, alllogicalsbasis, settings, _params);
+                reviseddualsimplex.dssoptimize(state.dss, settings, _params);
+                
+                //
+                // Export results
+                //
+                apserv.rvectorsetlengthatleast(ref state.xs, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.ys, m, _params);
+                apserv.ivectorsetlengthatleast(ref state.cs, n+m, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    state.xs[i] = state.dss.repx[i];
+                }
+                for(i=0; i<=m-1; i++)
+                {
+                    state.ys[i] = state.dss.repy[i];
+                }
+                for(i=0; i<=n+m-1; i++)
+                {
+                    state.cs[i] = state.dss.repstats[i];
+                }
+                state.repf = state.dss.repf;
+                state.repprimalerror = state.dss.repprimalerror;
+                state.repdualerror = state.dss.repdualerror;
+                state.repiterationscount = state.dss.repiterationscount;
+                state.repterminationtype = state.dss.repterminationtype;
+                return;
+            }
+            
+            //
+            // Integrity check failed - unknown solver
+            //
+            alglib.ap.assert(false, "MinQPOptimize: integrity check failed - unknown solver");
+        }
+
+
+        /*************************************************************************
+        LP solver results
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[N], solution. Filled by zeros on failure.
+            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.
+                        
+                        Failure codes returned by algorithm are:
+                        * -4    LP problem is primal unbounded (dual infeasible)
+                        * -3    LP problem is primal infeasible (dual unbounded)
+                        
+                        Success codes:
+                        *  1..4 successful completion
+                        *  5    MaxIts steps was taken
+
+          -- ALGLIB --
+             Copyright 11.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlpresults(minlpstate state,
+            ref double[] x,
+            minlpreport rep,
+            alglib.xparams _params)
+        {
+            x = new double[0];
+
+            minlpresultsbuf(state, ref x, rep, _params);
+        }
+
+
+        /*************************************************************************
+        LP results
+
+        Buffered implementation of MinLPResults() 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 minlpresultsbuf(minlpstate state,
+            ref double[] x,
+            minlpreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            if( alglib.ap.len(x)<state.n )
+            {
+                x = new double[state.n];
+            }
+            rep.y = new double[state.m];
+            rep.stats = new int[state.n+state.m];
+            rep.f = state.repf;
+            rep.primalerror = state.repprimalerror;
+            rep.dualerror = state.repdualerror;
+            rep.iterationscount = state.repiterationscount;
+            rep.terminationtype = state.repterminationtype;
+            if( state.repterminationtype>0 )
+            {
+                for(i=0; i<=state.n-1; i++)
+                {
+                    x[i] = state.xs[i];
+                }
+                for(i=0; i<=state.m-1; i++)
+                {
+                    rep.y[i] = state.ys[i];
+                }
+                for(i=0; i<=state.n+state.m-1; i++)
+                {
+                    rep.stats[i] = state.cs[i];
+                }
+            }
+            else
+            {
+                for(i=0; i<=state.n-1; i++)
+                {
+                    x[i] = 0;
+                }
+                for(i=0; i<=state.m-1; i++)
+                {
+                    rep.y[i] = 0;
+                }
+                for(i=0; i<=state.n+state.m-1; i++)
+                {
+                    rep.stats[i] = 0;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Clear report fields prior to the optimization.
+
+          -- ALGLIB --
+             Copyright 19.07.2018 by Bochkanov Sergey.
+        *************************************************************************/
+        private static void clearreportfields(minlpstate state,
+            alglib.xparams _params)
+        {
+            state.repf = 0.0;
+            state.repprimalerror = 0.0;
+            state.repdualerror = 0.0;
+            state.repiterationscount = 0;
+            state.repterminationtype = 0;
+        }
+
+
+    }
+    public class nlcslp
+    {
+        /*************************************************************************
+        This object stores temporaries of SLP subsolver.
+        *************************************************************************/
+        public class minslpsubsolver : apobject
+        {
+            public reviseddualsimplex.dualsimplexstate dss;
+            public reviseddualsimplex.dualsimplexsettings dsssettings;
+            public reviseddualsimplex.dualsimplexbasis lastbasis;
+            public bool basispresent;
+            public double[,] curd;
+            public int curdcnt;
+            public double[] curb;
+            public double[] curbndl;
+            public double[] curbndu;
+            public double[] cural;
+            public double[] curau;
+            public sparse.sparsematrix sparserawlc;
+            public sparse.sparsematrix sparseefflc;
+            public int hessiantype;
+            public double[,] h;
+            public double[,] curhd;
+            public double[,] densedummy;
+            public sparse.sparsematrix sparsedummy;
+            public double[] tmp0;
+            public double[] tmp1;
+            public double[] sk;
+            public double[] yk;
+            public minslpsubsolver()
+            {
+                init();
+            }
+            public override void init()
+            {
+                dss = new reviseddualsimplex.dualsimplexstate();
+                dsssettings = new reviseddualsimplex.dualsimplexsettings();
+                lastbasis = new reviseddualsimplex.dualsimplexbasis();
+                curd = new double[0,0];
+                curb = new double[0];
+                curbndl = new double[0];
+                curbndu = new double[0];
+                cural = new double[0];
+                curau = new double[0];
+                sparserawlc = new sparse.sparsematrix();
+                sparseefflc = new sparse.sparsematrix();
+                h = new double[0,0];
+                curhd = new double[0,0];
+                densedummy = new double[0,0];
+                sparsedummy = new sparse.sparsematrix();
+                tmp0 = new double[0];
+                tmp1 = new double[0];
+                sk = new double[0];
+                yk = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                minslpsubsolver _result = new minslpsubsolver();
+                _result.dss = (reviseddualsimplex.dualsimplexstate)dss.make_copy();
+                _result.dsssettings = (reviseddualsimplex.dualsimplexsettings)dsssettings.make_copy();
+                _result.lastbasis = (reviseddualsimplex.dualsimplexbasis)lastbasis.make_copy();
+                _result.basispresent = basispresent;
+                _result.curd = (double[,])curd.Clone();
+                _result.curdcnt = curdcnt;
+                _result.curb = (double[])curb.Clone();
+                _result.curbndl = (double[])curbndl.Clone();
+                _result.curbndu = (double[])curbndu.Clone();
+                _result.cural = (double[])cural.Clone();
+                _result.curau = (double[])curau.Clone();
+                _result.sparserawlc = (sparse.sparsematrix)sparserawlc.make_copy();
+                _result.sparseefflc = (sparse.sparsematrix)sparseefflc.make_copy();
+                _result.hessiantype = hessiantype;
+                _result.h = (double[,])h.Clone();
+                _result.curhd = (double[,])curhd.Clone();
+                _result.densedummy = (double[,])densedummy.Clone();
+                _result.sparsedummy = (sparse.sparsematrix)sparsedummy.make_copy();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmp1 = (double[])tmp1.Clone();
+                _result.sk = (double[])sk.Clone();
+                _result.yk = (double[])yk.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This object stores temporaries of SLP solver.
+        *************************************************************************/
+        public class minslpstate : apobject
+        {
+            public int n;
+            public int nec;
+            public int nic;
+            public int nlec;
+            public int nlic;
+            public double[,] scaledcleic;
+            public int[] lcsrcidx;
+            public bool[] hasbndl;
+            public bool[] hasbndu;
+            public double[] scaledbndl;
+            public double[] scaledbndu;
+            public double epsx;
+            public int maxits;
+            public int hessiantype;
+            public double[] x;
+            public double[] fi;
+            public double[,] j;
+            public double f;
+            public bool needfij;
+            public bool xupdated;
+            public double trustrad;
+            public double deltamax;
+            public minslpsubsolver subsolver;
+            public double[] d;
+            public double[] d0;
+            public double[] d1;
+            public linmin.linminstate mcstate;
+            public int xstagnationcnt;
+            public int fstagnationcnt;
+            public double[] prevx;
+            public double[] step0x;
+            public double[] stepkx;
+            public double[] stepkxc;
+            public double[] stepkxn;
+            public double[] step0fi;
+            public double[] stepkfi;
+            public double[] stepkfic;
+            public double[] stepkfin;
+            public double[,] step0j;
+            public double[,] stepkj;
+            public double[,] stepkjc;
+            public double[,] stepkjn;
+            public double stepklagval;
+            public double stepkclagval;
+            public double stepknlagval;
+            public double[] stepklaggrad;
+            public double[] stepknlaggrad;
+            public double[] stepklagmult;
+            public double[] stepknlagmult;
+            public double[] rho;
+            public double[] tmp0;
+            public double[] sclagtmp0;
+            public double[] sclagtmp1;
+            public double lastlcerr;
+            public int lastlcidx;
+            public double lastnlcerr;
+            public int lastnlcidx;
+            public double[] mftmp0;
+            public int repsimplexiterations;
+            public int repsimplexiterations1;
+            public int repsimplexiterations2;
+            public int repsimplexiterations3;
+            public int repinneriterationscount;
+            public int repouteriterationscount;
+            public int repterminationtype;
+            public double repbcerr;
+            public int repbcidx;
+            public double replcerr;
+            public int replcidx;
+            public double repnlcerr;
+            public int repnlcidx;
+            public rcommstate rstate;
+            public rcommstate rphase13state;
+            public rcommstate rphase2state;
+            public minslpstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                scaledcleic = new double[0,0];
+                lcsrcidx = new int[0];
+                hasbndl = new bool[0];
+                hasbndu = new bool[0];
+                scaledbndl = new double[0];
+                scaledbndu = new double[0];
+                x = new double[0];
+                fi = new double[0];
+                j = new double[0,0];
+                subsolver = new minslpsubsolver();
+                d = new double[0];
+                d0 = new double[0];
+                d1 = new double[0];
+                mcstate = new linmin.linminstate();
+                prevx = new double[0];
+                step0x = new double[0];
+                stepkx = new double[0];
+                stepkxc = new double[0];
+                stepkxn = new double[0];
+                step0fi = new double[0];
+                stepkfi = new double[0];
+                stepkfic = new double[0];
+                stepkfin = new double[0];
+                step0j = new double[0,0];
+                stepkj = new double[0,0];
+                stepkjc = new double[0,0];
+                stepkjn = new double[0,0];
+                stepklaggrad = new double[0];
+                stepknlaggrad = new double[0];
+                stepklagmult = new double[0];
+                stepknlagmult = new double[0];
+                rho = new double[0];
+                tmp0 = new double[0];
+                sclagtmp0 = new double[0];
+                sclagtmp1 = new double[0];
+                mftmp0 = new double[0];
+                rstate = new rcommstate();
+                rphase13state = new rcommstate();
+                rphase2state = new rcommstate();
+            }
+            public override alglib.apobject make_copy()
+            {
+                minslpstate _result = new minslpstate();
+                _result.n = n;
+                _result.nec = nec;
+                _result.nic = nic;
+                _result.nlec = nlec;
+                _result.nlic = nlic;
+                _result.scaledcleic = (double[,])scaledcleic.Clone();
+                _result.lcsrcidx = (int[])lcsrcidx.Clone();
+                _result.hasbndl = (bool[])hasbndl.Clone();
+                _result.hasbndu = (bool[])hasbndu.Clone();
+                _result.scaledbndl = (double[])scaledbndl.Clone();
+                _result.scaledbndu = (double[])scaledbndu.Clone();
+                _result.epsx = epsx;
+                _result.maxits = maxits;
+                _result.hessiantype = hessiantype;
+                _result.x = (double[])x.Clone();
+                _result.fi = (double[])fi.Clone();
+                _result.j = (double[,])j.Clone();
+                _result.f = f;
+                _result.needfij = needfij;
+                _result.xupdated = xupdated;
+                _result.trustrad = trustrad;
+                _result.deltamax = deltamax;
+                _result.subsolver = (minslpsubsolver)subsolver.make_copy();
+                _result.d = (double[])d.Clone();
+                _result.d0 = (double[])d0.Clone();
+                _result.d1 = (double[])d1.Clone();
+                _result.mcstate = (linmin.linminstate)mcstate.make_copy();
+                _result.xstagnationcnt = xstagnationcnt;
+                _result.fstagnationcnt = fstagnationcnt;
+                _result.prevx = (double[])prevx.Clone();
+                _result.step0x = (double[])step0x.Clone();
+                _result.stepkx = (double[])stepkx.Clone();
+                _result.stepkxc = (double[])stepkxc.Clone();
+                _result.stepkxn = (double[])stepkxn.Clone();
+                _result.step0fi = (double[])step0fi.Clone();
+                _result.stepkfi = (double[])stepkfi.Clone();
+                _result.stepkfic = (double[])stepkfic.Clone();
+                _result.stepkfin = (double[])stepkfin.Clone();
+                _result.step0j = (double[,])step0j.Clone();
+                _result.stepkj = (double[,])stepkj.Clone();
+                _result.stepkjc = (double[,])stepkjc.Clone();
+                _result.stepkjn = (double[,])stepkjn.Clone();
+                _result.stepklagval = stepklagval;
+                _result.stepkclagval = stepkclagval;
+                _result.stepknlagval = stepknlagval;
+                _result.stepklaggrad = (double[])stepklaggrad.Clone();
+                _result.stepknlaggrad = (double[])stepknlaggrad.Clone();
+                _result.stepklagmult = (double[])stepklagmult.Clone();
+                _result.stepknlagmult = (double[])stepknlagmult.Clone();
+                _result.rho = (double[])rho.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.sclagtmp0 = (double[])sclagtmp0.Clone();
+                _result.sclagtmp1 = (double[])sclagtmp1.Clone();
+                _result.lastlcerr = lastlcerr;
+                _result.lastlcidx = lastlcidx;
+                _result.lastnlcerr = lastnlcerr;
+                _result.lastnlcidx = lastnlcidx;
+                _result.mftmp0 = (double[])mftmp0.Clone();
+                _result.repsimplexiterations = repsimplexiterations;
+                _result.repsimplexiterations1 = repsimplexiterations1;
+                _result.repsimplexiterations2 = repsimplexiterations2;
+                _result.repsimplexiterations3 = repsimplexiterations3;
+                _result.repinneriterationscount = repinneriterationscount;
+                _result.repouteriterationscount = repouteriterationscount;
+                _result.repterminationtype = repterminationtype;
+                _result.repbcerr = repbcerr;
+                _result.repbcidx = repbcidx;
+                _result.replcerr = replcerr;
+                _result.replcidx = replcidx;
+                _result.repnlcerr = repnlcerr;
+                _result.repnlcidx = repnlcidx;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.rphase13state = (rcommstate)rphase13state.make_copy();
+                _result.rphase2state = (rcommstate)rphase2state.make_copy();
+                return _result;
+            }
+        };
+
+
+
+
+        public const double slpstpclosetozero = 0.001;
+        public const double slpdeltadecrease = 0.20;
+        public const double slpdeltaincrease = 0.80;
+        public const double slpstpclosetoone = 0.95;
+        public const double slpgtol = 0.4;
+        public const double bigc = 500.0;
+        public const double bfgstol = 1.0E-5;
+        public const double meritfunctiongain = 2.0;
+        public const double inequalitydampingfactor = 10.0;
+        public const double augmentationfactor = 10.0;
+        public const double inittrustrad = 0.1;
+        public const double stagnationepsf = 1.0E-12;
+        public const double stagnationepsx = 1.0E-12;
+        public const int xstagnationlimit = 3;
+        public const int fstagnationlimit = 5;
+        public const int nondescentlimit = 5;
+
+
+        public static void minslpinitbuf(double[] bndl,
+            double[] bndu,
+            double[] s,
+            double[] x0,
+            int n,
+            double[,] cleic,
+            int[] lcsrcidx,
+            int nec,
+            int nic,
+            int nlec,
+            int nlic,
+            double epsx,
+            int maxits,
+            minslpstate state,
+            alglib.xparams _params)
+        {
+            int nslack = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double vv = 0;
+
+            nslack = n+2*(nec+nlec)+(nic+nlic);
+            state.n = n;
+            state.nec = nec;
+            state.nic = nic;
+            state.nlec = nlec;
+            state.nlic = nlic;
+            
+            //
+            // Settings
+            //
+            state.hessiantype = 2;
+            
+            //
+            // Prepare RCOMM state
+            //
+            state.rstate.ia = new int[9+1];
+            state.rstate.ba = new bool[0+1];
+            state.rstate.ra = new double[7+1];
+            state.rstate.stage = -1;
+            state.needfij = false;
+            state.xupdated = false;
+            state.x = new double[n];
+            state.fi = new double[1+nlec+nlic];
+            state.j = new double[1+nlec+nlic, n];
+            
+            //
+            // Allocate memory.
+            //
+            apserv.rvectorsetlengthatleast(ref state.prevx, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.step0x, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.stepkx, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.stepkxc, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.stepkxn, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.step0fi, 1+nlec+nlic, _params);
+            apserv.rvectorsetlengthatleast(ref state.stepkfi, 1+nlec+nlic, _params);
+            apserv.rvectorsetlengthatleast(ref state.stepkfic, 1+nlec+nlic, _params);
+            apserv.rvectorsetlengthatleast(ref state.stepkfin, 1+nlec+nlic, _params);
+            apserv.rmatrixsetlengthatleast(ref state.step0j, 1+nlec+nlic, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.stepkj, 1+nlec+nlic, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.stepkjc, 1+nlec+nlic, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.stepkjn, 1+nlec+nlic, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.stepklaggrad, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.stepknlaggrad, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.rho, nec+nic+nlec+nlic, _params);
+            apserv.rvectorsetlengthatleast(ref state.stepklagmult, nec+nic+nlec+nlic, _params);
+            apserv.rvectorsetlengthatleast(ref state.stepknlagmult, nec+nic+nlec+nlic, _params);
+            apserv.bvectorsetlengthatleast(ref state.hasbndl, n, _params);
+            apserv.bvectorsetlengthatleast(ref state.hasbndu, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.scaledbndl, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.scaledbndu, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.scaledcleic, nec+nic, n+1, _params);
+            apserv.rvectorsetlengthatleast(ref state.d, nslack, _params);
+            apserv.rvectorsetlengthatleast(ref state.d0, nslack, _params);
+            apserv.rvectorsetlengthatleast(ref state.d1, nslack, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmp0, nslack, _params);
+            apserv.ivectorsetlengthatleast(ref state.lcsrcidx, nec+nic, _params);
+            
+            //
+            // Prepare scaled problem
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.hasbndl[i] = math.isfinite(bndl[i]);
+                state.hasbndu[i] = math.isfinite(bndu[i]);
+                if( state.hasbndl[i] )
+                {
+                    state.scaledbndl[i] = bndl[i]/s[i];
+                }
+                if( state.hasbndu[i] )
+                {
+                    state.scaledbndu[i] = bndu[i]/s[i];
+                }
+                if( state.hasbndl[i] && state.hasbndu[i] )
+                {
+                    alglib.ap.assert((double)(bndl[i])<=(double)(bndu[i]), "SLP: integrity check failed, box constraints are inconsistent");
+                }
+                state.step0x[i] = x0[i]/s[i];
+            }
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                
+                //
+                // Permutation
+                //
+                state.lcsrcidx[i] = lcsrcidx[i];
+                
+                //
+                // Scale and normalize linear constraints
+                //
+                vv = 0.0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = cleic[i,j]*s[j];
+                    state.scaledcleic[i,j] = v;
+                    vv = vv+v*v;
+                }
+                vv = Math.Sqrt(vv);
+                state.scaledcleic[i,n] = cleic[i,n];
+                if( (double)(vv)>(double)(0) )
+                {
+                    for(j=0; j<=n; j++)
+                    {
+                        state.scaledcleic[i,j] = state.scaledcleic[i,j]/vv;
+                    }
+                }
+            }
+            
+            //
+            // Initial enforcement of box constraints
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.hasbndl[i] )
+                {
+                    state.step0x[i] = Math.Max(state.step0x[i], state.scaledbndl[i]);
+                }
+                if( state.hasbndu[i] )
+                {
+                    state.step0x[i] = Math.Min(state.step0x[i], state.scaledbndu[i]);
+                }
+            }
+            
+            //
+            // Stopping criteria
+            //
+            state.epsx = epsx;
+            state.maxits = maxits;
+            
+            //
+            // Report fields
+            //
+            state.repsimplexiterations = 0;
+            state.repsimplexiterations1 = 0;
+            state.repsimplexiterations2 = 0;
+            state.repsimplexiterations3 = 0;
+            state.repterminationtype = 0;
+            state.repbcerr = 0;
+            state.repbcidx = -1;
+            state.replcerr = 0;
+            state.replcidx = -1;
+            state.repnlcerr = 0;
+            state.repnlcidx = -1;
+            state.repinneriterationscount = 0;
+            state.repouteriterationscount = 0;
+            
+            //
+            // Integrity checks:
+            // * it is important that significant step length is large enough that
+            //   we do not decrease trust regiod radius; it should also be small,
+            //   so we won't treat large steps as insignificant
+            //
+            alglib.ap.assert((double)(slpstpclosetozero)<(double)(slpdeltadecrease), "MinSLP: integrity check failed");
+            alglib.ap.assert((double)(slpdeltadecrease)<(double)(slpdeltaincrease), "MinSLP: integrity check failed");
+            alglib.ap.assert((double)(slpdeltaincrease)<(double)(slpstpclosetoone), "MinSLP: integrity check failed");
+        }
+
+
+        /*************************************************************************
+        This function performs actual processing for  SLP  algorithm.  It  expects
+        that caller redirects its reverse communication  requests NeedFiJ/XUpdated
+        to external user who will provide analytic derivative (or  handle  reports
+        about progress).
+
+        In case external user does not have analytic derivative, it is responsibility
+        of caller to intercept NeedFiJ request and  replace  it  with  appropriate
+        numerical differentiation scheme.
+
+        Results are stored:
+        * point - in State.StepKX
+
+        IMPORTANT: this function works with scaled problem formulation; it is
+                   responsibility of the caller to unscale request and scale
+                   Jacobian.
+                   
+        NOTE: SMonitor is expected to be correctly initialized smoothness monitor.
+
+          -- ALGLIB --
+             Copyright 05.03.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool minslpiteration(minslpstate state,
+            optserv.smoothnessmonitor smonitor,
+            bool userterminationneeded,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int nslack = 0;
+            int nec = 0;
+            int nic = 0;
+            int nlec = 0;
+            int nlic = 0;
+            int i = 0;
+            int j = 0;
+            int innerk = 0;
+            double v = 0;
+            double vv = 0;
+            double mx = 0;
+            bool lpstagesuccess = new bool();
+            double gammamax = 0;
+            double fscale = 0;
+            double f0 = 0;
+            double f1 = 0;
+            int status = 0;
+            double stp = 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];
+                nslack = state.rstate.ia[1];
+                nec = state.rstate.ia[2];
+                nic = state.rstate.ia[3];
+                nlec = state.rstate.ia[4];
+                nlic = state.rstate.ia[5];
+                i = state.rstate.ia[6];
+                j = state.rstate.ia[7];
+                innerk = state.rstate.ia[8];
+                status = state.rstate.ia[9];
+                lpstagesuccess = state.rstate.ba[0];
+                v = state.rstate.ra[0];
+                vv = state.rstate.ra[1];
+                mx = state.rstate.ra[2];
+                gammamax = state.rstate.ra[3];
+                fscale = state.rstate.ra[4];
+                f0 = state.rstate.ra[5];
+                f1 = state.rstate.ra[6];
+                stp = state.rstate.ra[7];
+            }
+            else
+            {
+                n = 359;
+                nslack = -58;
+                nec = -919;
+                nic = -909;
+                nlec = 81;
+                nlic = 255;
+                i = 74;
+                j = -788;
+                innerk = 809;
+                status = 205;
+                lpstagesuccess = false;
+                v = 939;
+                vv = -526;
+                mx = 763;
+                gammamax = -541;
+                fscale = -698;
+                f0 = -900;
+                f1 = -318;
+                stp = -940;
+            }
+            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
+            //
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            nlec = state.nlec;
+            nlic = state.nlic;
+            nslack = n+2*(nec+nlec)+(nic+nlic);
+            
+            //
+            // Prepare rcomm interface
+            //
+            state.needfij = false;
+            state.xupdated = false;
+            
+            //
+            // Initialize algorithm data:
+            // * Lagrangian and "Big C" estimates
+            // * trust region
+            // * vector of penalty coefficients Rho[], initialized by zeros
+            // * current approximation of the Hessian matrix H
+            // * initial linearized constraints
+            // * initial violation of linear/nonlinear constraints
+            //
+            state.xstagnationcnt = 0;
+            state.fstagnationcnt = 0;
+            state.trustrad = inittrustrad;
+            for(i=0; i<=nec+nic+nlec+nlic-1; i++)
+            {
+                state.rho[i] = 0.0;
+            }
+            gammamax = 0.0;
+            
+            //
+            // Avoid spurious warnings about possibly uninitialized vars
+            //
+            status = 0;
+            stp = 0;
+            
+            //
+            // Evaluate function vector and Jacobian at Step0X, send first location report.
+            // Compute initial violation of constraints.
+            //
+            slpsendx(state, state.step0x, _params);
+            state.needfij = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfij = false;
+            if( !slpretrievefij(state, state.step0fi, state.step0j, _params) )
+            {
+                
+                //
+                // Failed to retrieve function/Jaconian, infinities detected!
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    state.stepkx[i] = state.step0x[i];
+                }
+                state.repterminationtype = -8;
+                result = false;
+                return result;
+            }
+            slpcopystate(state, state.step0x, state.step0fi, state.step0j, state.stepkx, state.stepkfi, state.stepkj, _params);
+            slpsendx(state, state.stepkx, _params);
+            state.f = state.stepkfi[0];
+            state.xupdated = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.xupdated = false;
+            optserv.checklcviolation(state.scaledcleic, state.lcsrcidx, nec, nic, state.stepkx, n, ref state.replcerr, ref state.replcidx, _params);
+            optserv.checknlcviolation(state.stepkfi, nlec, nlic, ref state.repnlcerr, ref state.repnlcidx, _params);
+            
+            //
+            // Perform outer (NLC) iterations
+            //
+            tracebeginning(_params);
+            initlpsubsolver(state, state.subsolver, state.hessiantype, _params);
+        lbl_5:
+            if( false )
+            {
+                goto lbl_6;
+            }
+            
+            //
+            // Update vector Rho[] of quadratic penalty coefficients:
+            // * for Rho[i] corresponding to linear constraints we use gradient
+            //   norm at current point times augmentation factor (in 100-1000 range).
+            //   Everything is simple because linear constraints are normalized.
+            // * for Rho[i] corresponding to nonlinear constraints we use
+            //   gradient norm times augmentation factor divided by norm of
+            //   constraint gradient (or 1.0, if constraint gradient is less
+            //   than 1 in magnitude).
+            //
+            // Having large enough penalty coefficient is essential for the
+            // convergence of the algorithm from the infeasible initial points:
+            // it may fail to converge for very small Rho's because of inability
+            // to perform sufficiently large steps towards feasible area and against
+            // target function growth.
+            //
+            // Rho[] may never decrease, only increase. Values set at this point
+            // will retain through the entire NLP iteration, but will be updated
+            // before next NLP iteration will start.
+            //
+            v = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                v += state.stepkj[0,i_]*state.stepkj[0,i_];
+            }
+            fscale = Math.Max(Math.Sqrt(v), gammamax);
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                state.rho[i] = Math.Max(state.rho[i], augmentationfactor*fscale);
+            }
+            if( (double)(fscale)>(double)(0) )
+            {
+                for(i=0; i<=nlec+nlic-1; i++)
+                {
+                    vv = 0.0;
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        vv += state.stepkj[1+i,i_]*state.stepkj[1+i,i_];
+                    }
+                    vv = Math.Max(Math.Sqrt(vv), fscale);
+                    state.rho[nec+nic+i] = Math.Max(state.rho[nec+nic+i], augmentationfactor*(fscale/vv));
+                }
+            }
+            
+            //
+            // Save initial point for the outer iteration
+            //
+            slpcopystate(state, state.stepkx, state.stepkfi, state.stepkj, state.step0x, state.step0fi, state.step0j, _params);
+            
+            //
+            // Try to perform step using linear model with second order correction
+            //
+            // A second order correction helps to overcome Maratos effect - a tendency
+            // of L1 penalized merit function to reject nonzero steps along steepest
+            // descent direction.
+            //
+            // The idea (explained in more details in the Phase13Iteration() body)
+            // is to perform one look-ahead step and use updated constraint values
+            // back at the initial point.
+            //
+            state.rphase13state.ia = new int[8+1];
+            state.rphase13state.ra = new double[4+1];
+            state.rphase13state.stage = -1;
+        lbl_7:
+            if( !phase13iteration(state, smonitor, true, userterminationneeded, ref status, ref stp, _params) )
+            {
+                goto lbl_8;
+            }
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            goto lbl_7;
+        lbl_8:
+            if( status<0 )
+            {
+                goto lbl_5;
+            }
+            if( status==0 )
+            {
+                goto lbl_6;
+            }
+            
+            //
+            // If step with second order correction is shorter than 1.0, it means
+            // that target is sufficiently nonlinear to use advanced iterations.
+            // * perform inner LP subiterations with additional conjugacy constraints
+            // * perform polishing step
+            //
+            if( (double)(stp)>=(double)(slpstpclosetoone) )
+            {
+                goto lbl_9;
+            }
+            
+            //
+            // LP subiterations
+            //
+            state.rphase2state.ia = new int[11+1];
+            state.rphase2state.ra = new double[3+1];
+            state.rphase2state.stage = -1;
+        lbl_11:
+            if( !phase2iteration(state, smonitor, userterminationneeded, ref gammamax, ref status, _params) )
+            {
+                goto lbl_12;
+            }
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            goto lbl_11;
+        lbl_12:
+            if( status<0 )
+            {
+                goto lbl_5;
+            }
+            if( status==0 )
+            {
+                goto lbl_6;
+            }
+            
+            //
+            // Polishing step
+            //
+            // This step is essential because previous step (which minimizes
+            // Lagrangian) may fail to produce descent direction for L1-penalized merit function.
+            //
+            f0 = meritfunction(state, state.step0x, state.trustrad, state.step0fi, state.step0j, state.stepklagmult, _params);
+            f1 = meritfunction(state, state.stepkx, state.trustrad, state.stepkfi, state.stepkj, state.stepklagmult, _params);
+            if( (double)(f1)<(double)(f0) )
+            {
+                goto lbl_13;
+            }
+            
+            //
+            // Merit function does not decrease, perform "polishing" of results with
+            // merit function minimization
+            //
+            // We use same Phase13Iteration() function as in the beginning of the outer
+            // iteration, but this time without second order correction. Having
+            // two slightly different approaches helps us to perform good step
+            // due to diversity of the methods being utilized.
+            //
+            state.rphase13state.ia = new int[8+1];
+            state.rphase13state.ra = new double[4+1];
+            state.rphase13state.stage = -1;
+        lbl_15:
+            if( !phase13iteration(state, smonitor, false, userterminationneeded, ref status, ref stp, _params) )
+            {
+                goto lbl_16;
+            }
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            goto lbl_15;
+        lbl_16:
+            if( status<0 )
+            {
+                goto lbl_5;
+            }
+            if( status==0 )
+            {
+                goto lbl_6;
+            }
+        lbl_13:
+        lbl_9:
+            
+            //
+            // Multistep is done
+            //
+            tracemultistep(state, _params);
+            
+            //
+            // Update trust region
+            //
+            state.deltamax = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                state.deltamax = Math.Max(state.deltamax, Math.Abs(state.step0x[i]-state.stepkx[i])/state.trustrad);
+            }
+            if( (double)(state.deltamax)<=(double)(slpdeltadecrease) )
+            {
+                state.trustrad = state.trustrad*Math.Max(state.deltamax/slpdeltadecrease, 0.1);
+            }
+            if( (double)(state.deltamax)>=(double)(slpdeltaincrease) )
+            {
+                state.trustrad = state.trustrad*2.0;
+            }
+            
+            //
+            // Advance outer iteration counter, test stopping criteria
+            //
+            apserv.inc(ref state.repouteriterationscount, _params);
+            v = 0;
+            vv = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                state.prevx[i] = state.step0x[i];
+                v = v+math.sqr(state.step0x[i]-state.stepkx[i]);
+                vv = vv+math.sqr(state.stepkx[i]);
+            }
+            v = Math.Sqrt(v);
+            vv = Math.Sqrt(vv);
+            if( (double)(v)<=(double)(state.epsx) || (double)(v)<=(double)(stagnationepsx*vv) )
+            {
+                apserv.inc(ref state.xstagnationcnt, _params);
+            }
+            else
+            {
+                state.xstagnationcnt = 0;
+            }
+            if( (double)(Math.Abs(state.stepkfi[0]-state.step0fi[0]))<=(double)(stagnationepsf*Math.Max(Math.Abs(state.step0fi[0]), 1)) )
+            {
+                apserv.inc(ref state.fstagnationcnt, _params);
+            }
+            else
+            {
+                state.fstagnationcnt = 0;
+            }
+            tracenlpstep(state, _params);
+            if( state.xstagnationcnt>=xstagnationlimit )
+            {
+                state.repterminationtype = 2;
+                goto lbl_6;
+            }
+            if( state.maxits>0 && state.repinneriterationscount>=state.maxits )
+            {
+                state.repterminationtype = 5;
+                goto lbl_6;
+            }
+            if( state.fstagnationcnt>=fstagnationlimit )
+            {
+                state.repterminationtype = 7;
+                goto lbl_6;
+            }
+            goto lbl_5;
+        lbl_6:
+            tracefinalpoint(state, _params);
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = n;
+            state.rstate.ia[1] = nslack;
+            state.rstate.ia[2] = nec;
+            state.rstate.ia[3] = nic;
+            state.rstate.ia[4] = nlec;
+            state.rstate.ia[5] = nlic;
+            state.rstate.ia[6] = i;
+            state.rstate.ia[7] = j;
+            state.rstate.ia[8] = innerk;
+            state.rstate.ia[9] = status;
+            state.rstate.ba[0] = lpstagesuccess;
+            state.rstate.ra[0] = v;
+            state.rstate.ra[1] = vv;
+            state.rstate.ra[2] = mx;
+            state.rstate.ra[3] = gammamax;
+            state.rstate.ra[4] = fscale;
+            state.rstate.ra[5] = f0;
+            state.rstate.ra[6] = f1;
+            state.rstate.ra[7] = stp;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function initializes SLP subproblem.
+        Should be called once in the beginning of the optimization.
+
+        INPUT PARAMETERS:
+            SState          -   solver state
+            Subsolver       -   SLP subproblem to initialize
+            HessianType     -   0 for identity Hessian, 1 for BFGS update
+                                
+                                
+        RETURN VALUE:
+            True on success
+            False on failure of the LP solver (unexpected... but possible due to numerical errors)
+                                
+
+          -- ALGLIB --
+             Copyright 05.03.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void initlpsubsolver(minslpstate sstate,
+            minslpsubsolver subsolver,
+            int hessiantype,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int nslack = 0;
+            int nec = 0;
+            int nic = 0;
+            int nlec = 0;
+            int nlic = 0;
+            int lccnt = 0;
+            int nnz = 0;
+            int offs = 0;
+            int i = 0;
+            int j = 0;
+
+            n = sstate.n;
+            nec = sstate.nec;
+            nic = sstate.nic;
+            nlec = sstate.nlec;
+            nlic = sstate.nlic;
+            nslack = n+2*(nec+nlec)+(nic+nlic);
+            lccnt = nec+nic+nlec+nlic;
+            
+            //
+            // Create simplex solver.
+            //
+            // NOTE: we disable DSE pricing because it interferes with our
+            //       warm-start strategy.
+            //
+            reviseddualsimplex.dsssettingsinit(subsolver.dsssettings, _params);
+            subsolver.dsssettings.pricing = 0;
+            reviseddualsimplex.dssinit(nslack, subsolver.dss, _params);
+            
+            //
+            // Allocate temporaries
+            //
+            apserv.rvectorsetlengthatleast(ref subsolver.cural, lccnt+n, _params);
+            apserv.rvectorsetlengthatleast(ref subsolver.curau, lccnt+n, _params);
+            apserv.rmatrixsetlengthatleast(ref subsolver.curd, n, n, _params);
+            apserv.rmatrixsetlengthatleast(ref subsolver.curhd, n, n, _params);
+            apserv.rvectorsetlengthatleast(ref subsolver.curbndl, nslack, _params);
+            apserv.rvectorsetlengthatleast(ref subsolver.curbndu, nslack, _params);
+            apserv.rvectorsetlengthatleast(ref subsolver.curb, nslack, _params);
+            apserv.rvectorsetlengthatleast(ref subsolver.sk, n, _params);
+            apserv.rvectorsetlengthatleast(ref subsolver.yk, n, _params);
+            
+            //
+            // Initial state
+            //
+            subsolver.basispresent = false;
+            subsolver.curdcnt = 0;
+            subsolver.hessiantype = hessiantype;
+            if( hessiantype==1 || hessiantype==2 )
+            {
+                
+                //
+                // Prepare Hessian matrix
+                //
+                apserv.rmatrixsetlengthatleast(ref subsolver.h, n, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        subsolver.h[i,j] = 0;
+                    }
+                    subsolver.h[i,i] = 1;
+                }
+            }
+            
+            //
+            // Linear constraints do not change across subiterations, that's
+            // why we allocate storage for them at the start of the program.
+            //
+            // A full set of "raw" constraints is stored; later we will filter
+            // out inequality ones which are inactive anywhere in the current
+            // trust region.
+            //
+            // NOTE: because sparserawlc object stores only linear constraint
+            //       (linearizations of nonlinear ones are not stored) we
+            //       allocate only minimum necessary space.
+            //
+            nnz = 0;
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( sstate.scaledcleic[i,j]!=0.0 )
+                    {
+                        nnz = nnz+1;
+                    }
+                }
+            }
+            apserv.ivectorsetlengthatleast(ref subsolver.sparserawlc.ridx, nec+nic+1, _params);
+            apserv.rvectorsetlengthatleast(ref subsolver.sparserawlc.vals, nnz, _params);
+            apserv.ivectorsetlengthatleast(ref subsolver.sparserawlc.idx, nnz, _params);
+            apserv.ivectorsetlengthatleast(ref subsolver.sparserawlc.didx, nec+nic, _params);
+            apserv.ivectorsetlengthatleast(ref subsolver.sparserawlc.uidx, nec+nic, _params);
+            offs = 0;
+            subsolver.sparserawlc.ridx[0] = 0;
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( sstate.scaledcleic[i,j]!=0.0 )
+                    {
+                        
+                        //
+                        // Primary part of the matrix
+                        //
+                        subsolver.sparserawlc.vals[offs] = sstate.scaledcleic[i,j];
+                        subsolver.sparserawlc.idx[offs] = j;
+                        offs = offs+1;
+                    }
+                }
+                subsolver.sparserawlc.ridx[i+1] = offs;
+            }
+            subsolver.sparserawlc.matrixtype = 1;
+            subsolver.sparserawlc.ninitialized = subsolver.sparserawlc.ridx[nec+nic];
+            subsolver.sparserawlc.m = nec+nic;
+            subsolver.sparserawlc.n = n;
+            sparse.sparseinitduidx(subsolver.sparserawlc, _params);
+        }
+
+
+        /*************************************************************************
+        Restarts LP subproblem (cleans the matrix of internally stored directions)
+
+        INPUT PARAMETERS:
+            SState          -   solver state
+            Subsolver       -   SLP subproblem to initialize
+
+          -- ALGLIB --
+             Copyright 05.03.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void lpsubproblemrestart(minslpstate sstate,
+            minslpsubsolver subsolver,
+            alglib.xparams _params)
+        {
+            subsolver.curdcnt = 0;
+        }
+
+
+        /*************************************************************************
+        Updates Hessian estimate
+
+        INPUT PARAMETERS:
+            SState          -   solver state
+            Subsolver       -   SLP subproblem to initialize
+                                
+
+          -- ALGLIB --
+             Copyright 05.03.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static void lpsubproblemupdatehessian(minslpstate sstate,
+            minslpsubsolver subsolver,
+            double[] x0,
+            double[] g0,
+            double[] x1,
+            double[] g1,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+            double vv = 0;
+            double v = 0;
+            double v0 = 0;
+            double v1 = 0;
+            double v2 = 0;
+            double gk = 0;
+            double sk = 0;
+            double yk = 0;
+
+            n = sstate.n;
+            if( subsolver.hessiantype==1 || subsolver.hessiantype==2 )
+            {
+                apserv.rvectorsetlengthatleast(ref subsolver.tmp0, n, _params);
+                v = 0;
+                v0 = 0;
+                v1 = 0;
+                v2 = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    sk = x1[i]-x0[i];
+                    yk = g1[i]-g0[i];
+                    gk = g0[i];
+                    v = v+sk*yk;
+                    v0 = v0+sk*sk;
+                    v1 = v1+yk*yk;
+                    v2 = v2+gk*gk;
+                    subsolver.sk[i] = sk;
+                    subsolver.yk[i] = yk;
+                }
+                if( ((double)(Math.Sqrt(v0))>(double)(Math.Max(sstate.epsx, bfgstol)) && (double)(Math.Sqrt(v1))>(double)(bfgstol*Math.Sqrt(v2))) && (double)(v)>(double)(bfgstol*Math.Sqrt(v0)*Math.Sqrt(v1)) )
+                {
+                    
+                    //
+                    // Update Hessian if following criteria hold:
+                    // * MCINFO=1 (good step)
+                    // * step length is large enough
+                    // * |Yk| is large enough when compared with |G|
+                    // * (Sk,Yk) is large enough when compared with |S| and |G|
+                    //
+                    vv = ablas.rmatrixsyvmv(n, subsolver.h, 0, 0, true, subsolver.sk, 0, subsolver.tmp0, _params);
+                    ablas.rmatrixgemv(n, n, 1.0, subsolver.h, 0, 0, 0, subsolver.sk, 0, 0.0, subsolver.tmp0, 0, _params);
+                    ablas.rmatrixger(n, n, subsolver.h, 0, 0, 1/v, subsolver.yk, 0, subsolver.yk, 0, _params);
+                    ablas.rmatrixger(n, n, subsolver.h, 0, 0, -(1/vv), subsolver.tmp0, 0, subsolver.tmp0, 0, _params);
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function solves LP subproblem given by initial point X, function vector Fi
+        and Jacobian Jac, and returns estimates of Lagrangian multipliers and search direction D[].
+
+          -- ALGLIB --
+             Copyright 05.03.2018 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool lpsubproblemsolve(minslpstate state,
+            minslpsubsolver subsolver,
+            double[] x,
+            double[] fi,
+            double[,] jac,
+            int innerk,
+            double[] d,
+            double[] lagmult,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int nslack = 0;
+            int nec = 0;
+            int nic = 0;
+            int nlec = 0;
+            int nlic = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            double vv = 0;
+            double vright = 0;
+            double vmax = 0;
+            int basisinittype = 0;
+            int lccnt = 0;
+            int offsslackec = 0;
+            int offsslacknlec = 0;
+            int offsslackic = 0;
+            int offsslacknlic = 0;
+            int offs = 0;
+            int nnz = 0;
+            int j0 = 0;
+            int j1 = 0;
+
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            nlec = state.nlec;
+            nlic = state.nlic;
+            nslack = n+2*(nec+nlec)+(nic+nlic);
+            lccnt = nec+nic+nlec+nlic;
+            
+            //
+            // Locations of slack variables
+            //
+            offsslackec = n;
+            offsslacknlec = n+2*nec;
+            offsslackic = n+2*nec+2*nlec;
+            offsslacknlic = n+2*(nec+nlec)+nic;
+            
+            //
+            // Prepare temporary structures
+            //
+            apserv.rvectorgrowto(ref subsolver.cural, lccnt+subsolver.curdcnt, _params);
+            apserv.rvectorgrowto(ref subsolver.curau, lccnt+subsolver.curdcnt, _params);
+            
+            //
+            // Prepare default solution: all zeros
+            //
+            result = true;
+            for(i=0; i<=nslack-1; i++)
+            {
+                d[i] = 0.0;
+            }
+            for(i=0; i<=lccnt-1; i++)
+            {
+                lagmult[i] = 0;
+            }
+            
+            //
+            // Linear term B
+            //
+            // NOTE: elements [N,NSlack) are equal to bigC + perturbation to improve numeric properties of LP problem
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                subsolver.curb[i] = jac[0,i];
+            }
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(jac[0,i]);
+            }
+            v = apserv.coalesce(Math.Sqrt(v), 1.0, _params);
+            for(i=n; i<=nslack-1; i++)
+            {
+                subsolver.curb[i] = (bigc+1.0/(1+i))*v;
+            }
+            
+            //
+            // Trust radius constraints for primary variables
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                subsolver.curbndl[i] = -state.trustrad;
+                subsolver.curbndu[i] = state.trustrad;
+                if( state.hasbndl[i] )
+                {
+                    subsolver.curbndl[i] = Math.Max(subsolver.curbndl[i], state.scaledbndl[i]-x[i]);
+                }
+                if( state.hasbndu[i] )
+                {
+                    subsolver.curbndu[i] = Math.Min(subsolver.curbndu[i], state.scaledbndu[i]-x[i]);
+                }
+            }
+            
+            //
+            // Prepare storage for "effective" constraining matrix
+            //
+            nnz = subsolver.sparserawlc.ridx[nec+nic];
+            for(i=0; i<=nlec+nlic-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    if( jac[1+i,j]!=0.0 )
+                    {
+                        nnz = nnz+1;
+                    }
+                }
+            }
+            nnz = nnz+2*nec+nic;
+            nnz = nnz+2*nlec+nlic;
+            nnz = nnz+subsolver.curdcnt*n;
+            apserv.ivectorgrowto(ref subsolver.sparseefflc.ridx, lccnt+n+1, _params);
+            apserv.rvectorgrowto(ref subsolver.sparseefflc.vals, nnz, _params);
+            apserv.ivectorgrowto(ref subsolver.sparseefflc.idx, nnz, _params);
+            apserv.ivectorsetlengthatleast(ref subsolver.sparseefflc.didx, lccnt+n, _params);
+            apserv.ivectorsetlengthatleast(ref subsolver.sparseefflc.uidx, lccnt+n, _params);
+            subsolver.sparseefflc.m = 0;
+            subsolver.sparseefflc.n = nslack;
+            subsolver.sparseefflc.matrixtype = 1;
+            
+            //
+            // Append linear equality/inequality constraints
+            //
+            // Scan sparsified linear constraints stored in sparserawlc[], skip ones
+            // which are inactive anywhere in the trust region.
+            //
+            apserv.rvectorsetlengthatleast(ref subsolver.tmp0, nslack, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                subsolver.tmp0[i] = x[i];
+            }
+            for(i=n; i<=nslack-1; i++)
+            {
+                subsolver.tmp0[i] = 0;
+            }
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                
+                //
+                // Calculate:
+                // * VRight - product of X[] (extended with zeros up to NSlack elements)
+                //            and AR[i] - Ith row of sparserawlc matrix.
+                // * VMax   - maximum value of X*ARi computed over trust region
+                //
+                vright = 0;
+                vmax = 0;
+                j0 = subsolver.sparserawlc.ridx[i];
+                j1 = subsolver.sparserawlc.ridx[i+1]-1;
+                for(k=j0; k<=j1; k++)
+                {
+                    j = subsolver.sparserawlc.idx[k];
+                    v = subsolver.tmp0[j];
+                    vv = subsolver.sparserawlc.vals[k];
+                    vright = vright+vv*v;
+                    if( vv>=0 )
+                    {
+                        vmax = vmax+vv*(v+subsolver.curbndu[j]);
+                    }
+                    else
+                    {
+                        vmax = vmax+vv*(v+subsolver.curbndl[j]);
+                    }
+                }
+                
+                //
+                // If constraint is an inequality one and guaranteed to be inactive
+                // within trust region, it is skipped (row itself is retained but
+                // filled by zeros).
+                //
+                if( i>=nec && vmax<=state.scaledcleic[i,n] )
+                {
+                    offs = subsolver.sparseefflc.ridx[i];
+                    subsolver.sparseefflc.vals[offs] = -1;
+                    subsolver.sparseefflc.idx[offs] = offsslackic+(i-nec);
+                    subsolver.sparseefflc.ridx[i+1] = offs+1;
+                    subsolver.cural[i] = 0.0;
+                    subsolver.curau[i] = 0.0;
+                    subsolver.curbndl[offsslackic+(i-nec)] = 0;
+                    subsolver.curbndu[offsslackic+(i-nec)] = 0;
+                    continue;
+                }
+                
+                //
+                // Start working on row I
+                //
+                offs = subsolver.sparseefflc.ridx[i];
+                
+                //
+                // Copy constraint from sparserawlc[] to sparseefflc[]
+                //
+                j0 = subsolver.sparserawlc.ridx[i];
+                j1 = subsolver.sparserawlc.ridx[i+1]-1;
+                for(k=j0; k<=j1; k++)
+                {
+                    subsolver.sparseefflc.idx[offs] = subsolver.sparserawlc.idx[k];
+                    subsolver.sparseefflc.vals[offs] = subsolver.sparserawlc.vals[k];
+                    offs = offs+1;
+                }
+                
+                //
+                // Set up slack variables
+                //
+                if( i<nec )
+                {
+                    subsolver.sparseefflc.vals[offs+0] = -1;
+                    subsolver.sparseefflc.vals[offs+1] = 1;
+                    subsolver.sparseefflc.idx[offs+0] = offsslackec+2*i+0;
+                    subsolver.sparseefflc.idx[offs+1] = offsslackec+2*i+1;
+                    offs = offs+2;
+                }
+                else
+                {
+                    
+                    //
+                    // Slack variables for inequality constraints
+                    //
+                    subsolver.sparseefflc.vals[offs] = -1;
+                    subsolver.sparseefflc.idx[offs] = offsslackic+(i-nec);
+                    offs = offs+1;
+                }
+                
+                //
+                // Finalize row
+                //
+                subsolver.sparseefflc.ridx[i+1] = offs;
+                
+                //
+                // Set up bounds.
+                //
+                // NOTE: bounds for equality and inequality constraints are
+                //       handled differently
+                //
+                v = vright-state.scaledcleic[i,n];
+                if( i<nec )
+                {
+                    subsolver.cural[i] = -v;
+                    subsolver.curau[i] = -v;
+                    subsolver.curbndl[offsslackec+2*i+0] = 0;
+                    subsolver.curbndl[offsslackec+2*i+1] = 0;
+                    subsolver.curbndu[offsslackec+2*i+0] = Math.Abs(v);
+                    subsolver.curbndu[offsslackec+2*i+1] = Math.Abs(v);
+                }
+                else
+                {
+                    subsolver.cural[i] = Double.NegativeInfinity;
+                    subsolver.curau[i] = -v;
+                    subsolver.curbndl[offsslackic+(i-nec)] = 0;
+                    subsolver.curbndu[offsslackic+(i-nec)] = Math.Max(v, 0);
+                }
+            }
+            subsolver.sparseefflc.m = subsolver.sparseefflc.m+(nec+nic);
+            
+            //
+            // Append nonlinear equality/inequality constraints
+            //
+            for(i=0; i<=nlec+nlic-1; i++)
+            {
+                
+                //
+                // Calculate scale coefficient
+                //
+                vv = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = jac[1+i,j];
+                    vv = vv+v*v;
+                }
+                vv = 1/apserv.coalesce(Math.Sqrt(vv), 1, _params);
+                
+                //
+                // Copy scaled row
+                //
+                offs = subsolver.sparseefflc.ridx[subsolver.sparseefflc.m+i];
+                for(j=0; j<=n-1; j++)
+                {
+                    if( jac[1+i,j]!=0.0 )
+                    {
+                        subsolver.sparseefflc.vals[offs] = vv*jac[1+i,j];
+                        subsolver.sparseefflc.idx[offs] = j;
+                        offs = offs+1;
+                    }
+                }
+                if( i<nlec )
+                {
+                    
+                    //
+                    // Add slack terms for equality constraints
+                    //
+                    subsolver.sparseefflc.vals[offs+0] = -1;
+                    subsolver.sparseefflc.vals[offs+1] = 1;
+                    subsolver.sparseefflc.idx[offs+0] = offsslacknlec+2*i+0;
+                    subsolver.sparseefflc.idx[offs+1] = offsslacknlec+2*i+1;
+                    offs = offs+2;
+                }
+                else
+                {
+                    
+                    //
+                    // Add slack terms for inequality constraints
+                    //
+                    subsolver.sparseefflc.vals[offs] = -1;
+                    subsolver.sparseefflc.idx[offs] = offsslacknlic+(i-nlec);
+                    offs = offs+1;
+                }
+                subsolver.sparseefflc.ridx[subsolver.sparseefflc.m+i+1] = offs;
+                
+                //
+                // Set box constraints on slack variables and bounds on linear equality/inequality constraints
+                //
+                v = vv*fi[1+i];
+                if( i<nlec )
+                {
+                    
+                    //
+                    // Equality constraint
+                    //
+                    subsolver.cural[subsolver.sparseefflc.m+i] = -v;
+                    subsolver.curau[subsolver.sparseefflc.m+i] = -v;
+                    subsolver.curbndl[offsslacknlec+2*i+0] = 0;
+                    subsolver.curbndl[offsslacknlec+2*i+1] = 0;
+                    subsolver.curbndu[offsslacknlec+2*i+0] = Math.Abs(v);
+                    subsolver.curbndu[offsslacknlec+2*i+1] = Math.Abs(v);
+                }
+                else
+                {
+                    
+                    //
+                    // Inequality constraint
+                    //
+                    subsolver.cural[subsolver.sparseefflc.m+i] = Double.NegativeInfinity;
+                    subsolver.curau[subsolver.sparseefflc.m+i] = -v;
+                    subsolver.curbndl[offsslacknlic+(i-nlec)] = 0;
+                    subsolver.curbndu[offsslacknlic+(i-nlec)] = Math.Max(v, 0);
+                }
+            }
+            subsolver.sparseefflc.m = subsolver.sparseefflc.m+(nlec+nlic);
+            
+            //
+            // Append conjugacy constraints
+            //
+            for(i=0; i<=subsolver.curdcnt-1; i++)
+            {
+                
+                //
+                // Copy N elements of CurHD
+                //
+                // NOTE: we expect product of D and H to be dense, so we copy all N elements
+                //
+                v = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    vv = subsolver.curhd[i,j];
+                    v = v+vv*vv;
+                }
+                v = 1.0/apserv.coalesce(Math.Sqrt(v), 1.0, _params);
+                offs = subsolver.sparseefflc.ridx[subsolver.sparseefflc.m];
+                for(j=0; j<=n-1; j++)
+                {
+                    vv = subsolver.curhd[i,j];
+                    subsolver.sparseefflc.vals[offs] = v*vv;
+                    subsolver.sparseefflc.idx[offs] = j;
+                    offs = offs+1;
+                }
+                subsolver.sparseefflc.ridx[subsolver.sparseefflc.m+1] = offs;
+                
+                //
+                // Set bounds on linear constraints
+                //
+                subsolver.cural[subsolver.sparseefflc.m] = 0;
+                subsolver.curau[subsolver.sparseefflc.m] = 0;
+                
+                //
+                // Increase row count
+                //
+                subsolver.sparseefflc.m = subsolver.sparseefflc.m+1;
+            }
+            
+            //
+            // Finalize sparse matrix structure
+            //
+            alglib.ap.assert(subsolver.sparseefflc.ridx[subsolver.sparseefflc.m]<=alglib.ap.len(subsolver.sparseefflc.idx), "LPSubproblemSolve: critical integrity check failed");
+            alglib.ap.assert(subsolver.sparseefflc.ridx[subsolver.sparseefflc.m]<=alglib.ap.len(subsolver.sparseefflc.vals), "LPSubproblemSolve: critical integrity check failed");
+            subsolver.sparseefflc.ninitialized = subsolver.sparseefflc.ridx[subsolver.sparseefflc.m];
+            sparse.sparseinitduidx(subsolver.sparseefflc, _params);
+            
+            //
+            // Choose dual simplex method basis initialization type
+            //
+            if( innerk==1 && subsolver.basispresent )
+            {
+                basisinittype = 2;
+            }
+            else
+            {
+                basisinittype = 1;
+            }
+            
+            //
+            // Solve linear program
+            //
+            apserv.rvectorsetlengthatleast(ref subsolver.tmp0, nslack, _params);
+            for(i=0; i<=nslack-1; i++)
+            {
+                subsolver.tmp0[i] = state.trustrad;
+            }
+            reviseddualsimplex.dsssetproblem(subsolver.dss, subsolver.curb, subsolver.curbndl, subsolver.curbndu, subsolver.tmp0, subsolver.densedummy, subsolver.sparseefflc, 1, subsolver.cural, subsolver.curau, subsolver.sparseefflc.m, subsolver.lastbasis, basisinittype, subsolver.dsssettings, _params);
+            reviseddualsimplex.dssoptimize(subsolver.dss, subsolver.dsssettings, _params);
+            state.repsimplexiterations = state.repsimplexiterations+subsolver.dss.repiterationscount;
+            state.repsimplexiterations1 = state.repsimplexiterations1+subsolver.dss.repiterationscount1;
+            state.repsimplexiterations2 = state.repsimplexiterations2+subsolver.dss.repiterationscount2;
+            state.repsimplexiterations3 = state.repsimplexiterations3+subsolver.dss.repiterationscount3;
+            if( subsolver.dss.repterminationtype<=0 )
+            {
+                
+                //
+                // LP solver failed due to numerical errors; exit
+                //
+                result = false;
+                return result;
+            }
+            if( innerk==1 )
+            {
+                
+                //
+                // Store basis
+                //
+                reviseddualsimplex.dssexportbasis(subsolver.dss, subsolver.lastbasis, _params);
+                subsolver.basispresent = true;
+            }
+            
+            //
+            // Extract direction D[] and Lagrange multipliers
+            //
+            for(i=0; i<=nslack-1; i++)
+            {
+                d[i] = subsolver.dss.repx[i];
+            }
+            for(i=0; i<=lccnt-1; i++)
+            {
+                lagmult[i] = -subsolver.dss.repdx[i];
+            }
+            
+            //
+            // Update matrix of products H*Dprev
+            //
+            alglib.ap.assert(subsolver.curdcnt<alglib.ap.rows(subsolver.curd), "SLP: CurD is too small");
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Store direction and default conjugacy constraint d'*I*Dprev=0
+                //
+                subsolver.curd[subsolver.curdcnt,i] = d[i];
+                subsolver.curhd[subsolver.curdcnt,i] = d[i];
+            }
+            apserv.inc(ref subsolver.curdcnt, _params);
+            if( state.hessiantype==1 )
+            {
+                
+                //
+                // Conjugacy constraint d*H*Dprev=0, full recomputation of (H*Dprev)
+                //
+                ablas.rmatrixgemm(subsolver.curdcnt, n, n, 1.0, subsolver.curd, 0, 0, 0, subsolver.h, 0, 0, 0, 0.0, subsolver.curhd, 0, 0, _params);
+            }
+            if( state.hessiantype==2 )
+            {
+                
+                //
+                // Conjugacy constraint d*H*Dprev=0, only last row of (H*Dprev) is recomputed
+                //
+                apserv.rvectorsetlengthatleast(ref subsolver.tmp0, n, _params);
+                ablas.rmatrixgemv(n, n, 1.0, subsolver.h, 0, 0, 0, d, 0, 0.0, subsolver.tmp0, 0, _params);
+                for(j=0; j<=n-1; j++)
+                {
+                    subsolver.curhd[subsolver.curdcnt-1,j] = subsolver.tmp0[j];
+                }
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function tries to perform either phase #1 or phase #3 step.
+
+        Former corresponds to linear model step (without conjugacy constraints) with
+        correction for nonlinearity ("second order correction").  Such  correction
+        helps to overcome  Maratos  effect  (a  tendency  of  L1  penalized  merit
+        functions to reject nonzero steps).
+
+        Latter is a step using linear model with no second order correction.
+
+        INPUT PARAMETERS:
+            State       -   SLP solver state, StepKX/Fi/J is used as current point,
+                            StepK...N/C fields are modified.
+            SMonitor    -   smoothness monitor
+            UseCorrection-  True if we perform second order correction
+            UserTerminationNeeded-True if user requested termination
+
+        OUTPUT PARAMETERS:
+            State       -   StepKX/Fi/J advanced to new point, StepKLagMult contain
+                            current Lagrange multipliers, RepTerminationType is
+                            set to current termination code (if Status=0).
+            Status      -   when reverse communication is done, Status is set to:
+                            * negative value,  if   we  have   to  restart   outer
+                              iteration
+                            * positive value,  if we can proceed to the next stage
+                              of the outer iteration
+                            * zero, if algorithm is terminated (RepTerminationType
+                              is set to appropriate value)
+            Stp         -   step length, in [0,1]
+
+          -- ALGLIB --
+             Copyright 05.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool phase13iteration(minslpstate state,
+            optserv.smoothnessmonitor smonitor,
+            bool usecorrection,
+            bool userterminationneeded,
+            ref int status,
+            ref double stp,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int nslack = 0;
+            int nec = 0;
+            int nic = 0;
+            int nlec = 0;
+            int nlic = 0;
+            int innerk = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double f0 = 0;
+            double f1 = 0;
+            double nu = 0;
+            double localstp = 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.rphase13state.stage>=0 )
+            {
+                n = state.rphase13state.ia[0];
+                nslack = state.rphase13state.ia[1];
+                nec = state.rphase13state.ia[2];
+                nic = state.rphase13state.ia[3];
+                nlec = state.rphase13state.ia[4];
+                nlic = state.rphase13state.ia[5];
+                innerk = state.rphase13state.ia[6];
+                i = state.rphase13state.ia[7];
+                j = state.rphase13state.ia[8];
+                v = state.rphase13state.ra[0];
+                f0 = state.rphase13state.ra[1];
+                f1 = state.rphase13state.ra[2];
+                nu = state.rphase13state.ra[3];
+                localstp = state.rphase13state.ra[4];
+            }
+            else
+            {
+                n = 1016;
+                nslack = -229;
+                nec = -536;
+                nic = 487;
+                nlec = -115;
+                nlic = 886;
+                innerk = 346;
+                i = -722;
+                j = -413;
+                v = -461;
+                f0 = 927;
+                f1 = 201;
+                nu = 922;
+                localstp = -154;
+            }
+            if( state.rphase13state.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rphase13state.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rphase13state.stage==2 )
+            {
+                goto lbl_2;
+            }
+            
+            //
+            // Routine body
+            //
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            nlec = state.nlec;
+            nlic = state.nlic;
+            nslack = n+2*(nec+nlec)+(nic+nlic);
+            innerk = 1;
+            
+            //
+            // Default decision is to continue algorithm
+            //
+            status = 1;
+            stp = 0;
+            
+            //
+            // Determine step direction using linearized model with no conjugacy terms
+            //
+            lpsubproblemrestart(state, state.subsolver, _params);
+            if( !lpsubproblemsolve(state, state.subsolver, state.stepkx, state.stepkfi, state.stepkj, innerk, state.d0, state.stepklagmult, _params) )
+            {
+                
+                //
+                // Can not find descent direction for Lagrangian
+                //
+                state.trustrad = 0.5*state.trustrad;
+                if( (double)(state.trustrad)<(double)(state.epsx) )
+                {
+                    state.repterminationtype = 2;
+                    status = 0;
+                }
+                else
+                {
+                    status = -1;
+                }
+                result = false;
+                return result;
+            }
+            
+            //
+            // Compute second order correction if required. The issue we address here
+            // is a tendency of L1 penalized function to reject steps built using simple
+            // linearized model when nonlinear constraints change faster than the target.
+            //
+            // The idea is that we perform trial step (stp=1) using simple linearized model,
+            // compute constraint vector at the new trial point - and use these updated
+            // constraint linearizations back at the initial point.
+            //
+            // As result, we have:
+            // * D0 - "simple" step without correction
+            // * D1 - a correction to D0, such that D0+D1 points exactly to the solution
+            //   of the "corrected" linear model.
+            //
+            if( !usecorrection )
+            {
+                goto lbl_3;
+            }
+            
+            //
+            // Perform trial step using vector D0 to StepKXC
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.stepkxc[i] = state.stepkx[i]+state.d0[i];
+            }
+            slpsendx(state, state.stepkxc, _params);
+            state.needfij = true;
+            state.rphase13state.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfij = false;
+            if( !slpretrievefij(state, state.stepkfic, state.stepkjc, _params) )
+            {
+                
+                //
+                // Failed to retrieve func/Jac, infinities detected
+                //
+                state.repterminationtype = -8;
+                status = 0;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Move back to point StepKX, restore original linearization of the target
+            //
+            state.stepkfic[0] = state.stepkfi[0];
+            for(j=0; j<=n-1; j++)
+            {
+                state.stepkxc[j] = state.stepkx[j];
+                state.stepkjc[0,j] = state.stepkj[0,j];
+            }
+            
+            //
+            // Extrapolate linearization of nonlinear constraints back to origin
+            //
+            for(i=1; i<=nlec+nlic; i++)
+            {
+                v = 0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+state.d0[j]*state.stepkjc[i,j];
+                }
+                state.stepkfic[i] = state.stepkfic[i]-v;
+            }
+            
+            //
+            // Solve linearized problem one more time, now with new linearization of constraints
+            // (but still old linearization of the target), obtain D1
+            //
+            lpsubproblemrestart(state, state.subsolver, _params);
+            if( !lpsubproblemsolve(state, state.subsolver, state.stepkxc, state.stepkfic, state.stepkjc, innerk, state.d1, state.stepknlagmult, _params) )
+            {
+                
+                //
+                // Can not find descent direction for Lagrangian
+                //
+                state.trustrad = 0.5*state.trustrad;
+                if( (double)(state.trustrad)<(double)(state.epsx) )
+                {
+                    state.repterminationtype = 2;
+                    status = 0;
+                }
+                else
+                {
+                    status = -1;
+                }
+                result = false;
+                return result;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.d1[i] = state.d1[i]-state.d0[i];
+            }
+            goto lbl_4;
+        lbl_3:
+            
+            //
+            // No second order correction, D1=0
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.d1[i] = 0;
+            }
+        lbl_4:
+            
+            //
+            // Perform merit function backtracking line search, with trial point being
+            // computed as XN = XK + Stp*D0 + Stp^2*D1, with Stp in [0,1]
+            //
+            // NOTE: we use StepKLagMult - Lagrange multipliers computed for initial,
+            //       uncorrected task - for the merit function model.
+            //       Using StepKNLagMult can destabilize algorithm.
+            //
+            localstp = 1.0;
+            nu = 0.5;
+            f0 = meritfunction(state, state.stepkx, state.trustrad, state.stepkfi, state.stepkj, state.stepklagmult, _params);
+            optserv.smoothnessmonitorstartlinesearch(smonitor, state.stepkx, state.stepkfi, state.stepkj, _params);
+        lbl_5:
+            if( false )
+            {
+                goto lbl_6;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.stepkxn[i] = state.stepkx[i]+localstp*state.d0[i]+localstp*localstp*state.d1[i];
+            }
+            slpsendx(state, state.stepkxn, _params);
+            state.needfij = true;
+            state.rphase13state.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.needfij = false;
+            if( !slpretrievefij(state, state.stepkfin, state.stepkjn, _params) )
+            {
+                
+                //
+                // Failed to retrieve func/Jac, infinities detected
+                //
+                state.repterminationtype = -8;
+                status = 0;
+                result = false;
+                return result;
+            }
+            optserv.smoothnessmonitorenqueuepoint(smonitor, state.d0, localstp, state.stepkxn, state.stepkfin, state.stepkjn, _params);
+            f1 = meritfunction(state, state.stepkxn, state.trustrad, state.stepkfin, state.stepkjn, state.stepklagmult, _params);
+            if( (double)(f1)<(double)(f0) )
+            {
+                
+                //
+                // Step is found!
+                //
+                goto lbl_6;
+            }
+            if( (double)(localstp)<(double)(0.001) )
+            {
+                
+                //
+                // Step is shorter than 0.001 times current search direction,
+                // it means that no good step can be found.
+                //
+                localstp = 0;
+                slpcopystate(state, state.stepkx, state.stepkfi, state.stepkj, state.stepkxn, state.stepkfin, state.stepkjn, _params);
+                goto lbl_6;
+            }
+            localstp = nu*localstp;
+            nu = Math.Max(0.1, 0.5*nu);
+            goto lbl_5;
+        lbl_6:
+            optserv.smoothnessmonitorfinalizelinesearch(smonitor, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.hasbndl[i] )
+                {
+                    state.stepkxn[i] = Math.Max(state.stepkxn[i], state.scaledbndl[i]);
+                }
+                if( state.hasbndu[i] )
+                {
+                    state.stepkxn[i] = Math.Min(state.stepkxn[i], state.scaledbndu[i]);
+                }
+            }
+            if( userterminationneeded )
+            {
+                
+                //
+                // User requested termination, break before we move to new point
+                //
+                state.repterminationtype = 8;
+                status = 0;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Move to new point
+            //
+            stp = localstp;
+            slpcopystate(state, state.stepkxn, state.stepkfin, state.stepkjn, state.stepkx, state.stepkfi, state.stepkj, _params);
+            if( (double)(localstp)<=(double)(0) )
+            {
+                goto lbl_7;
+            }
+            
+            //
+            // Report one more inner iteration
+            //
+            apserv.inc(ref state.repinneriterationscount, _params);
+            slpsendx(state, state.stepkx, _params);
+            state.f = state.stepkfi[0];
+            state.xupdated = true;
+            state.rphase13state.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.xupdated = false;
+            
+            //
+            // Update constraint violations
+            //
+            optserv.checklcviolation(state.scaledcleic, state.lcsrcidx, nec, nic, state.stepkx, n, ref state.replcerr, ref state.replcidx, _params);
+            optserv.checknlcviolation(state.stepkfi, nlec, nlic, ref state.repnlcerr, ref state.repnlcidx, _params);
+        lbl_7:
+            tracemeritsearch(state, _params);
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rphase13state.ia[0] = n;
+            state.rphase13state.ia[1] = nslack;
+            state.rphase13state.ia[2] = nec;
+            state.rphase13state.ia[3] = nic;
+            state.rphase13state.ia[4] = nlec;
+            state.rphase13state.ia[5] = nlic;
+            state.rphase13state.ia[6] = innerk;
+            state.rphase13state.ia[7] = i;
+            state.rphase13state.ia[8] = j;
+            state.rphase13state.ra[0] = v;
+            state.rphase13state.ra[1] = f0;
+            state.rphase13state.ra[2] = f1;
+            state.rphase13state.ra[3] = nu;
+            state.rphase13state.ra[4] = localstp;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function tries to perform phase #2 iterations.
+
+        Phase #2 is  a  sequence  of  linearized  steps   minimizing  L2-penalized
+        Lagrangian  performed  with  successively  increasing  set  of   conjugacy
+        constraints (which make algorithm behavior similar to that of CG).
+
+        INPUT PARAMETERS:
+            State       -   SLP solver state, StepKX/Fi/J is used as current point,
+                            StepK...N/C fields are modified.
+            SMonitor    -   smoothness monitor
+            UserTerminationNeeded-True if user requested termination
+            GammaMax    -   current estimate of the Hessian norm
+
+        OUTPUT PARAMETERS:
+            State       -   StepKX/Fi/J advanced to new point, StepKLagMult contain
+                            current Lagrange multipliers, RepTerminationType is
+                            set to current termination code (if Status=0).
+            GammaMax    -   updated estimate of the Hessian norm    
+            Status      -   when reverse communication is done, Status is set to:
+                            * negative value,  if   we  have   to  restart   outer
+                              iteration
+                            * positive value,  if we can proceed to the next stage
+                              of the outer iteration
+                            * zero, if algorithm is terminated (RepTerminationType
+                              is set to appropriate value)
+
+          -- ALGLIB --
+             Copyright 05.02.2019 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool phase2iteration(minslpstate state,
+            optserv.smoothnessmonitor smonitor,
+            bool userterminationneeded,
+            ref double gammamax,
+            ref int status,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int nslack = 0;
+            int nec = 0;
+            int nic = 0;
+            int nlec = 0;
+            int nlic = 0;
+            double stp = 0;
+            int mcinfo = 0;
+            int mcnfev = 0;
+            int mcstage = 0;
+            int i = 0;
+            int innerk = 0;
+            double v = 0;
+            double vv = 0;
+            double mx = 0;
+            int nondescentcnt = 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.rphase2state.stage>=0 )
+            {
+                n = state.rphase2state.ia[0];
+                nslack = state.rphase2state.ia[1];
+                nec = state.rphase2state.ia[2];
+                nic = state.rphase2state.ia[3];
+                nlec = state.rphase2state.ia[4];
+                nlic = state.rphase2state.ia[5];
+                mcinfo = state.rphase2state.ia[6];
+                mcnfev = state.rphase2state.ia[7];
+                mcstage = state.rphase2state.ia[8];
+                i = state.rphase2state.ia[9];
+                innerk = state.rphase2state.ia[10];
+                nondescentcnt = state.rphase2state.ia[11];
+                stp = state.rphase2state.ra[0];
+                v = state.rphase2state.ra[1];
+                vv = state.rphase2state.ra[2];
+                mx = state.rphase2state.ra[3];
+            }
+            else
+            {
+                n = 306;
+                nslack = -1011;
+                nec = 951;
+                nic = -463;
+                nlec = 88;
+                nlic = -861;
+                mcinfo = -678;
+                mcnfev = -731;
+                mcstage = -675;
+                i = -763;
+                innerk = -233;
+                nondescentcnt = -936;
+                stp = -279;
+                v = 94;
+                vv = -812;
+                mx = 427;
+            }
+            if( state.rphase2state.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rphase2state.stage==1 )
+            {
+                goto lbl_1;
+            }
+            
+            //
+            // Routine body
+            //
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            nlec = state.nlec;
+            nlic = state.nlic;
+            nslack = n+2*(nec+nlec)+(nic+nlic);
+            
+            //
+            // The default decision is to continue iterations
+            //
+            status = 1;
+            
+            //
+            // Perform inner LP subiterations.
+            //
+            // During this process we maintain information about several points:
+            // * point #0, initial one, with "step0" prefix
+            // * point #K, last one of current LP session, with "stepk" prefix
+            // * additionally we have point #KN, current candidate during line search at step K.
+            //
+            // For each point we store:
+            // * location X (scaled coordinates)
+            // * function vector Fi (target function + nonlinear constraints)
+            // * scaled Jacobian J
+            //
+            nondescentcnt = 0;
+            lpsubproblemrestart(state, state.subsolver, _params);
+            innerk = 1;
+        lbl_2:
+            if( innerk>n )
+            {
+                goto lbl_4;
+            }
+            
+            //
+            // Formulate LP subproblem and solve it
+            //
+            tracelpstart(_params);
+            if( !lpsubproblemsolve(state, state.subsolver, state.stepkx, state.stepkfi, state.stepkj, innerk, state.d, state.stepklagmult, _params) )
+            {
+                
+                //
+                // LP solver failed due to numerical errors; exit.
+                // It may happen when we solve problem with LOTS of conjugacy constraints.
+                //
+                tracelpfailure(_params);
+                status = -1;
+                if( innerk==1 )
+                {
+                    
+                    //
+                    // The very first iteration failed, really strange.
+                    // Let's decrease trust radius and try one more time.
+                    //
+                    state.trustrad = 0.5*state.trustrad;
+                    if( (double)(state.trustrad)<(double)(state.epsx) )
+                    {
+                        state.repterminationtype = 2;
+                        status = 0;
+                    }
+                }
+                result = false;
+                return result;
+            }
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.d[i]);
+            }
+            if( (double)(v)==(double)(0) )
+            {
+                
+                //
+                // Zero step is suggested (maybe we arrived exactly to the solution), stop iterations
+                //
+                status = 1;
+                slpcopystate(state, state.stepkx, state.stepkfi, state.stepkj, state.stepkxn, state.stepkfin, state.stepkjn, _params);
+                tracelpstep(state, 0, 0, 0, _params);
+                result = false;
+                return result;
+            }
+            
+            //
+            // Perform line search to minimize Lagrangian along D.
+            // Post-normalize StepKXN with respect to box constraints.
+            //
+            // MCSRCH can fail in the following cases:
+            // * rounding errors prevent optimization
+            // * non-descent direction is specified (MCINFO=0 is returned)
+            // In the latter case we proceed to minimization of merit function.
+            //
+            // NOTE: constraint violation reports are updated during Lagrangian computation
+            //
+            state.lastlcerr = 0;
+            state.lastlcidx = -1;
+            state.lastnlcerr = 0;
+            state.lastnlcidx = -1;
+            apserv.rvectorsetlengthatleast(ref state.tmp0, n, _params);
+            lagrangianfg(state, state.stepkx, state.trustrad, state.stepkfi, state.stepkj, state.stepklagmult, ref state.stepklagval, state.stepklaggrad, ref state.lastlcerr, ref state.lastlcidx, ref state.lastnlcerr, ref state.lastnlcidx, _params);
+            slpcopystate(state, state.stepkx, state.stepkfi, state.stepkj, state.stepkxn, state.stepkfin, state.stepkjn, _params);
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                state.stepknlaggrad[i] = state.stepklaggrad[i];
+                v = v+state.d[i]*state.stepklaggrad[i];
+            }
+            if( (double)(v)>=(double)(0) )
+            {
+                
+                //
+                // Non-descent direction D was specified; it may happen because LP subproblem favors
+                // directions which decrease L1 penalty and default augmentation of Lagrangian involves
+                // only L2 term.
+                //
+                // We make several retries with conjugate directions before giving up.
+                //
+                tracelpnondescent(_params);
+                apserv.inc(ref nondescentcnt, _params);
+                if( nondescentcnt>nondescentlimit )
+                {
+                    status = 1;
+                    result = false;
+                    return result;
+                }
+                goto lbl_3;
+            }
+            optserv.smoothnessmonitorstartlinesearch(smonitor, state.stepkx, state.stepkfi, state.stepkj, _params);
+            state.stepknlagval = state.stepklagval;
+            mcnfev = 0;
+            mcstage = 0;
+            stp = 1.0;
+            linmin.mcsrch(n, ref state.stepkxn, ref state.stepknlagval, ref state.stepknlaggrad, state.d, ref stp, 1.0, slpgtol, ref mcinfo, ref mcnfev, ref state.tmp0, state.mcstate, ref mcstage, _params);
+        lbl_5:
+            if( mcstage==0 )
+            {
+                goto lbl_6;
+            }
+            slpsendx(state, state.stepkxn, _params);
+            state.needfij = true;
+            state.rphase2state.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfij = false;
+            if( !slpretrievefij(state, state.stepkfin, state.stepkjn, _params) )
+            {
+                
+                //
+                // Failed to retrieve func/Jac, infinities detected
+                //
+                status = 0;
+                state.repterminationtype = -8;
+                result = false;
+                return result;
+            }
+            optserv.smoothnessmonitorenqueuepoint(smonitor, state.d, stp, state.stepkxn, state.stepkfin, state.stepkjn, _params);
+            lagrangianfg(state, state.stepkxn, state.trustrad, state.stepkfin, state.stepkjn, state.stepklagmult, ref state.stepknlagval, state.stepknlaggrad, ref state.lastlcerr, ref state.lastlcidx, ref state.lastnlcerr, ref state.lastnlcidx, _params);
+            linmin.mcsrch(n, ref state.stepkxn, ref state.stepknlagval, ref state.stepknlaggrad, state.d, ref stp, 1.0, slpgtol, ref mcinfo, ref mcnfev, ref state.tmp0, state.mcstate, ref mcstage, _params);
+            goto lbl_5;
+        lbl_6:
+            optserv.smoothnessmonitorfinalizelinesearch(smonitor, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.hasbndl[i] )
+                {
+                    state.stepkxn[i] = Math.Max(state.stepkxn[i], state.scaledbndl[i]);
+                }
+                if( state.hasbndu[i] )
+                {
+                    state.stepkxn[i] = Math.Min(state.stepkxn[i], state.scaledbndu[i]);
+                }
+            }
+            tracelpstep(state, stp, mcinfo, mcnfev, _params);
+            if( mcinfo<=0 )
+            {
+                
+                //
+                // Line search failed miserably, terminate
+                //
+                status = 1;
+                if( innerk==1 )
+                {
+                    
+                    //
+                    // The very first iteration failed, really strange.
+                    // Let's decrease trust radius and try one more time.
+                    //
+                    state.trustrad = 0.5*state.trustrad;
+                    if( (double)(state.trustrad)<(double)(state.epsx) )
+                    {
+                        state.repterminationtype = 2;
+                        status = 0;
+                    }
+                }
+                result = false;
+                return result;
+            }
+            if( mcinfo==1 )
+            {
+                lpsubproblemupdatehessian(state, state.subsolver, state.stepkx, state.stepklaggrad, state.stepkxn, state.stepknlaggrad, _params);
+            }
+            
+            //
+            // Update GammaMax - estimate of the function Hessian norm
+            //
+            v = 0;
+            vv = 0;
+            mx = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                mx = Math.Max(mx, Math.Abs(state.stepkxn[i]-state.stepkx[i]));
+                v = v+math.sqr(state.stepkxn[i]-state.stepkx[i]);
+                vv = vv+(state.stepkjn[0,i]-state.stepkj[0,i])*(state.stepkxn[i]-state.stepkx[i]);
+            }
+            if( (double)(mx)>(double)(bfgstol) )
+            {
+                gammamax = Math.Max(gammamax, Math.Abs(vv/v));
+            }
+            
+            //
+            // Check status of the termination request
+            // Update current point
+            // Update constraint status.
+            // Report iteration.
+            //
+            if( userterminationneeded )
+            {
+                
+                //
+                // User requested termination, break before we move to new point
+                //
+                status = 0;
+                state.repterminationtype = 8;
+                tracenlpstep(state, _params);
+                tracefinalpoint(state, _params);
+                result = false;
+                return result;
+            }
+            slpcopystate(state, state.stepkxn, state.stepkfin, state.stepkjn, state.stepkx, state.stepkfi, state.stepkj, _params);
+            state.replcerr = state.lastlcerr;
+            state.replcidx = state.lastlcidx;
+            state.repnlcerr = state.lastnlcerr;
+            state.repnlcidx = state.lastnlcidx;
+            apserv.inc(ref state.repinneriterationscount, _params);
+            slpsendx(state, state.stepkx, _params);
+            state.f = state.stepkfi[0];
+            state.xupdated = true;
+            state.rphase2state.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.xupdated = false;
+            
+            //
+            // Terminate inner LP subiterations
+            //
+            if( state.maxits>0 && state.repinneriterationscount>=state.maxits )
+            {
+                
+                //
+                // Iteration limit exhausted
+                //
+                status = 1;
+                result = false;
+                return result;
+            }
+            if( (double)(stp)<=(double)(slpstpclosetozero) )
+            {
+                
+                //
+                // Step is too close to zero, terminate iterations.
+                //
+                status = 1;
+                result = false;
+                return result;
+            }
+            if( (double)(stp)>=(double)(slpstpclosetoone) )
+            {
+                
+                //
+                // Step is close to 1.0, either of two is likely:
+                // * we move through nearly linear region of F()
+                // * we try to enforce some strongly violated constraint
+                //
+                // In any case, authors of the original algorithm recommend to break inner LP
+                // iteration and proceed to test of sufficient decrease of merit function.
+                //
+                status = 1;
+                result = false;
+                return result;
+            }
+            if( (mcinfo!=1 && mcinfo!=3) && mcinfo!=5 )
+            {
+                
+                //
+                // Line search ended with "bad" MCINFO
+                // (neither sufficient decrease, neither maximum step);
+                // terminate.
+                //
+                status = 1;
+                result = false;
+                return result;
+            }
+            for(i=n; i<=nslack-1; i++)
+            {
+                if( state.d[i]>0 )
+                {
+                    
+                    //
+                    // Some relaxation variable is non-zero;
+                    // break - it means that we are far away from the feasible area.
+                    //
+                    status = 1;
+                    result = false;
+                    return result;
+                }
+            }
+        lbl_3:
+            innerk = innerk+1;
+            goto lbl_2;
+        lbl_4:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rphase2state.ia[0] = n;
+            state.rphase2state.ia[1] = nslack;
+            state.rphase2state.ia[2] = nec;
+            state.rphase2state.ia[3] = nic;
+            state.rphase2state.ia[4] = nlec;
+            state.rphase2state.ia[5] = nlic;
+            state.rphase2state.ia[6] = mcinfo;
+            state.rphase2state.ia[7] = mcnfev;
+            state.rphase2state.ia[8] = mcstage;
+            state.rphase2state.ia[9] = i;
+            state.rphase2state.ia[10] = innerk;
+            state.rphase2state.ia[11] = nondescentcnt;
+            state.rphase2state.ra[0] = stp;
+            state.rphase2state.ra[1] = v;
+            state.rphase2state.ra[2] = vv;
+            state.rphase2state.ra[3] = mx;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Copies X to State.X
+        *************************************************************************/
+        private static void slpsendx(minslpstate state,
+            double[] xs,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = state.n;
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.hasbndl[i] && xs[i]<=state.scaledbndl[i] )
+                {
+                    state.x[i] = state.scaledbndl[i];
+                    continue;
+                }
+                if( state.hasbndu[i] && xs[i]>=state.scaledbndu[i] )
+                {
+                    state.x[i] = state.scaledbndu[i];
+                    continue;
+                }
+                state.x[i] = xs[i];
+            }
+        }
+
+
+        /*************************************************************************
+        Retrieves F-vector and scaled Jacobian, copies them to FiS and JS.
+
+        Returns True on success, False on failure (when F or J are not finite numbers).
+        *************************************************************************/
+        private static bool slpretrievefij(minslpstate state,
+            double[] fis,
+            double[,] js,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int nlec = 0;
+            int nlic = 0;
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            n = state.n;
+            nlec = state.nlec;
+            nlic = state.nlic;
+            v = 0;
+            for(i=0; i<=nlec+nlic; i++)
+            {
+                fis[i] = state.fi[i];
+                v = 0.1*v+state.fi[i];
+                for(j=0; j<=n-1; j++)
+                {
+                    js[i,j] = state.j[i,j];
+                    v = 0.1*v+state.j[i,j];
+                }
+            }
+            result = math.isfinite(v);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Copies state (X point, Fi vector, J jacobian) to preallocated storage.
+        *************************************************************************/
+        private static void slpcopystate(minslpstate state,
+            double[] x0,
+            double[] fi0,
+            double[,] j0,
+            double[] x1,
+            double[] fi1,
+            double[,] j1,
+            alglib.xparams _params)
+        {
+            int nlec = 0;
+            int nlic = 0;
+            int n = 0;
+            int i = 0;
+            int j = 0;
+
+            n = state.n;
+            nlec = state.nlec;
+            nlic = state.nlic;
+            for(i=0; i<=n-1; i++)
+            {
+                x1[i] = x0[i];
+            }
+            for(i=0; i<=nlec+nlic; i++)
+            {
+                fi1[i] = fi0[i];
+                for(j=0; j<=n-1; j++)
+                {
+                    j1[i,j] = j0[i,j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function calculates Lagrangian of the problem (in scaled variables):
+        its value and gradient.
+
+        Additionally it also estimates violation of linear constraints at the point
+        as well as index of the most violated constraint
+        *************************************************************************/
+        private static void lagrangianfg(minslpstate state,
+            double[] x,
+            double trustrad,
+            double[] fi,
+            double[,] j,
+            double[] lagmult,
+            ref double f,
+            double[] g,
+            ref double lcerr,
+            ref int lcidx,
+            ref double nlcerr,
+            ref int nlcidx,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+            int nec = 0;
+            int nic = 0;
+            int nlec = 0;
+            int nlic = 0;
+            double v = 0;
+            double vlag = 0;
+            double vact = 0;
+            double vd = 0;
+            double vviolate = 0;
+            double rhoi = 0;
+            bool usesparsegemv = new bool();
+            double dampingfactor = 0;
+
+            f = 0;
+            lcerr = 0;
+            lcidx = 0;
+            nlcerr = 0;
+            nlcidx = 0;
+
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            nlec = state.nlec;
+            nlic = state.nlic;
+            dampingfactor = inequalitydampingfactor/trustrad;
+            
+            //
+            // Prepare constraint violation report
+            //
+            lcerr = 0;
+            lcidx = -1;
+            nlcerr = 0;
+            nlcidx = -1;
+            
+            //
+            // Target function
+            //
+            f = fi[0];
+            for(i=0; i<=n-1; i++)
+            {
+                g[i] = j[0,i];
+            }
+            
+            //
+            // Lagrangian terms for linear constraints, constraint violations
+            //
+            if( nec+nic>0 )
+            {
+                usesparsegemv = state.subsolver.sparserawlc.ridx[nec+nic]<apserv.sparselevel2density(_params)*n*(nec+nic);
+                apserv.rvectorsetlengthatleast(ref state.sclagtmp0, Math.Max(nec+nic, n), _params);
+                apserv.rvectorsetlengthatleast(ref state.sclagtmp1, Math.Max(nec+nic, n), _params);
+                if( usesparsegemv )
+                {
+                    sparse.sparsemv(state.subsolver.sparserawlc, x, ref state.sclagtmp0, _params);
+                }
+                else
+                {
+                    ablas.rmatrixgemv(nec+nic, n, 1.0, state.scaledcleic, 0, 0, 0, x, 0, 0.0, state.sclagtmp0, 0, _params);
+                }
+                for(i=0; i<=nec+nic-1; i++)
+                {
+                    
+                    //
+                    // Estimate constraint value at the point, update violation info
+                    //
+                    // NOTE: here we expect that scaledCLEIC[] has normalized rows
+                    //
+                    v = state.sclagtmp0[i]-state.scaledcleic[i,n];
+                    if( i<nec || v>0 )
+                    {
+                        
+                        //
+                        // Either equality constraint or violated inequality one.
+                        // Update violation report.
+                        //
+                        vviolate = Math.Abs(v);
+                        if( vviolate>lcerr )
+                        {
+                            lcerr = vviolate;
+                            lcidx = state.lcsrcidx[i];
+                        }
+                    }
+                    
+                    //
+                    // Prepare
+                    //
+                    vlag = lagmult[i];
+                    state.sclagtmp1[i] = 0;
+                    
+                    //
+                    // Primary Lagrangian term
+                    //
+                    if( i<nec || v>0 )
+                    {
+                        vact = v;
+                        vd = 1;
+                    }
+                    else
+                    {
+                        vd = 1/(1-dampingfactor*v);
+                        vact = v*vd;
+                        vd = vd*vd;
+                    }
+                    f = f+vlag*vact;
+                    state.sclagtmp1[i] = state.sclagtmp1[i]+vlag*vd;
+                    
+                    //
+                    // Quadratic augmentation term
+                    //
+                    if( i<nec || v>0 )
+                    {
+                        vact = v;
+                    }
+                    else
+                    {
+                        vact = 0;
+                    }
+                    rhoi = apserv.coalesce(state.rho[i], 1.0, _params);
+                    f = f+0.5*rhoi*vact*vact;
+                    state.sclagtmp1[i] = state.sclagtmp1[i]+rhoi*vact;
+                }
+                if( usesparsegemv )
+                {
+                    sparse.sparsemtv(state.subsolver.sparserawlc, state.sclagtmp1, ref state.sclagtmp0, _params);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        g[i] = g[i]+state.sclagtmp0[i];
+                    }
+                }
+                else
+                {
+                    ablas.rmatrixgemv(n, nec+nic, 1.0, state.scaledcleic, 0, 0, 1, state.sclagtmp1, 0, 1.0, g, 0, _params);
+                }
+            }
+            
+            //
+            // Lagrangian terms for nonlinear constraints
+            //
+            apserv.rvectorsetlengthatleast(ref state.sclagtmp1, nlec+nlic, _params);
+            for(i=0; i<=nlec+nlic-1; i++)
+            {
+                v = fi[1+i];
+                if( i<nlec || v>0 )
+                {
+                    
+                    //
+                    // Either equality constraint or violated inequality one.
+                    // Update violation report.
+                    //
+                    vviolate = Math.Abs(v);
+                    if( vviolate>nlcerr )
+                    {
+                        nlcerr = vviolate;
+                        nlcidx = i;
+                    }
+                }
+                vlag = lagmult[nec+nic+i];
+                state.sclagtmp1[i] = 0;
+                
+                //
+                // Lagrangian term
+                //
+                if( i<nlec || v>0 )
+                {
+                    vact = v;
+                    vd = 1;
+                }
+                else
+                {
+                    vd = 1/(1-dampingfactor*v);
+                    vact = v*vd;
+                    vd = vd*vd;
+                }
+                f = f+vlag*vact;
+                state.sclagtmp1[i] = state.sclagtmp1[i]+vlag*vd;
+                
+                //
+                // Augmentation term
+                //
+                if( i<nlec || v>0 )
+                {
+                    vact = v;
+                }
+                else
+                {
+                    vact = 0;
+                }
+                rhoi = apserv.coalesce(state.rho[nec+nic+i], 1.0, _params);
+                f = f+0.5*rhoi*vact*vact;
+                state.sclagtmp1[i] = state.sclagtmp1[i]+rhoi*vact;
+            }
+            ablas.rmatrixgemv(n, nlec+nlic, 1.0, j, 1, 0, 1, state.sclagtmp1, 0, 1.0, g, 0, _params);
+        }
+
+
+        /*************************************************************************
+        This function calculates L1-penalized merit function.
+        *************************************************************************/
+        private static double meritfunction(minslpstate state,
+            double[] x,
+            double trustrad,
+            double[] fi,
+            double[,] j,
+            double[] lagmult,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            int i = 0;
+            int n = 0;
+            int nec = 0;
+            int nic = 0;
+            int nlec = 0;
+            int nlic = 0;
+            double v = 0;
+
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            nlec = state.nlec;
+            nlic = state.nlic;
+            
+            //
+            // Merit function: primary term
+            //
+            result = fi[0];
+            
+            //
+            // Merit function: augmentation and penalty for linear constraints
+            //
+            apserv.rvectorsetlengthatleast(ref state.mftmp0, nec+nic, _params);
+            ablas.rmatrixgemv(nec+nic, n, 1.0, state.scaledcleic, 0, 0, 0, x, 0, 0.0, state.mftmp0, 0, _params);
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                v = state.mftmp0[i]-state.scaledcleic[i,n];
+                if( i<nec )
+                {
+                    
+                    //
+                    // Augmentation term
+                    //
+                    result = result+0.5*state.rho[i]*v*v;
+                    
+                    //
+                    // L1 penalty term
+                    //
+                    result = result+meritfunctiongain*Math.Abs(lagmult[i]*v);
+                }
+                else
+                {
+                    
+                    //
+                    // Augmentation term
+                    //
+                    result = result+0.5*state.rho[i]*math.sqr(Math.Max(v, 0));
+                    
+                    //
+                    // L1 penalty term
+                    //
+                    result = result+meritfunctiongain*Math.Max(lagmult[i]*v, 0);
+                }
+            }
+            
+            //
+            // Merit function: augmentation and penalty for nonlinear constraints
+            //
+            for(i=0; i<=nlec+nlic-1; i++)
+            {
+                v = fi[1+i];
+                if( i<nlec )
+                {
+                    
+                    //
+                    // Augmentation term
+                    //
+                    result = result+0.5*state.rho[nec+nic+i]*v*v;
+                    
+                    //
+                    // L1 Penalty term
+                    //
+                    result = result+meritfunctiongain*Math.Abs(lagmult[nec+nic+i]*v);
+                }
+                else
+                {
+                    
+                    //
+                    // Augmentation term
+                    //
+                    result = result+0.5*state.rho[nec+nic+i]*math.sqr(Math.Max(v, 0));
+                    
+                    //
+                    // Penalty term
+                    //
+                    result = result+meritfunctiongain*Math.Max(lagmult[nec+nic+i]*v, 0);
+                }
+            }
+            return result;
+        }
+
+
+        private static void tracebeginning(alglib.xparams _params)
+        {
+        }
+
+
+        private static void tracelpstart(alglib.xparams _params)
+        {
+        }
+
+
+        private static void tracelpfailure(alglib.xparams _params)
+        {
+        }
+
+
+        private static void tracelpnondescent(alglib.xparams _params)
+        {
+        }
+
+
+        private static void tracelpstep(minslpstate state,
+            double stp,
+            int i,
+            int f,
+            alglib.xparams _params)
+        {
+        }
+
+
+        private static void tracemeritsearch(minslpstate state,
+            alglib.xparams _params)
+        {
+        }
+
+
+        private static void tracemultistep(minslpstate state,
+            alglib.xparams _params)
+        {
+        }
+
+
+        private static void tracenlpstep(minslpstate state,
+            alglib.xparams _params)
+        {
+        }
+
+
+        private static void tracefinalpoint(minslpstate state,
+            alglib.xparams _params)
+        {
+        }
+
+
+    }
+    public class minnlc
+    {
+        /*************************************************************************
+        This object stores nonlinear optimizer state.
+        You should use functions provided by MinNLC subpackage to work  with  this
+        object
+        *************************************************************************/
+        public class minnlcstate : apobject
+        {
+            public double stabilizingpoint;
+            public double initialinequalitymultiplier;
+            public int solvertype;
+            public int prectype;
+            public int updatefreq;
+            public double rho;
+            public int n;
+            public double epsx;
+            public int maxits;
+            public int aulitscnt;
+            public bool xrep;
+            public double stpmax;
+            public double diffstep;
+            public double teststep;
+            public double[] s;
+            public double[] bndl;
+            public double[] bndu;
+            public bool[] hasbndl;
+            public bool[] hasbndu;
+            public int nec;
+            public int nic;
+            public double[,] cleic;
+            public int[] lcsrcidx;
+            public int ng;
+            public int nh;
+            public double[] x;
+            public double f;
+            public double[] fi;
+            public double[,] j;
+            public bool needfij;
+            public bool needfi;
+            public bool xupdated;
+            public rcommstate rstate;
+            public rcommstate rstateaul;
+            public rcommstate rstateslp;
+            public double[] scaledbndl;
+            public double[] scaledbndu;
+            public double[,] scaledcleic;
+            public double[] xc;
+            public double[] xstart;
+            public double[] xbase;
+            public double[] fbase;
+            public double[] dfbase;
+            public double[] fm2;
+            public double[] fm1;
+            public double[] fp1;
+            public double[] fp2;
+            public double[] dfm1;
+            public double[] dfp1;
+            public double[] bufd;
+            public double[] bufc;
+            public double[] tmp0;
+            public double[,] bufw;
+            public double[,] bufz;
+            public double[] xk;
+            public double[] xk1;
+            public double[] gk;
+            public double[] gk1;
+            public double gammak;
+            public bool xkpresent;
+            public minlbfgs.minlbfgsstate auloptimizer;
+            public minlbfgs.minlbfgsreport aulreport;
+            public double[] nubc;
+            public double[] nulc;
+            public double[] nunlc;
+            public bool userterminationneeded;
+            public nlcslp.minslpstate slpsolverstate;
+            public int smoothnessguardlevel;
+            public optserv.smoothnessmonitor smonitor;
+            public double[] lastscaleused;
+            public int repinneriterationscount;
+            public int repouteriterationscount;
+            public int repnfev;
+            public int repterminationtype;
+            public double repbcerr;
+            public int repbcidx;
+            public double replcerr;
+            public int replcidx;
+            public double repnlcerr;
+            public int repnlcidx;
+            public int repdbgphase0its;
+            public minnlcstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                s = new double[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                hasbndl = new bool[0];
+                hasbndu = new bool[0];
+                cleic = new double[0,0];
+                lcsrcidx = new int[0];
+                x = new double[0];
+                fi = new double[0];
+                j = new double[0,0];
+                rstate = new rcommstate();
+                rstateaul = new rcommstate();
+                rstateslp = new rcommstate();
+                scaledbndl = new double[0];
+                scaledbndu = new double[0];
+                scaledcleic = new double[0,0];
+                xc = new double[0];
+                xstart = new double[0];
+                xbase = new double[0];
+                fbase = new double[0];
+                dfbase = new double[0];
+                fm2 = new double[0];
+                fm1 = new double[0];
+                fp1 = new double[0];
+                fp2 = new double[0];
+                dfm1 = new double[0];
+                dfp1 = new double[0];
+                bufd = new double[0];
+                bufc = new double[0];
+                tmp0 = new double[0];
+                bufw = new double[0,0];
+                bufz = new double[0,0];
+                xk = new double[0];
+                xk1 = new double[0];
+                gk = new double[0];
+                gk1 = new double[0];
+                auloptimizer = new minlbfgs.minlbfgsstate();
+                aulreport = new minlbfgs.minlbfgsreport();
+                nubc = new double[0];
+                nulc = new double[0];
+                nunlc = new double[0];
+                slpsolverstate = new nlcslp.minslpstate();
+                smonitor = new optserv.smoothnessmonitor();
+                lastscaleused = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                minnlcstate _result = new minnlcstate();
+                _result.stabilizingpoint = stabilizingpoint;
+                _result.initialinequalitymultiplier = initialinequalitymultiplier;
+                _result.solvertype = solvertype;
+                _result.prectype = prectype;
+                _result.updatefreq = updatefreq;
+                _result.rho = rho;
+                _result.n = n;
+                _result.epsx = epsx;
+                _result.maxits = maxits;
+                _result.aulitscnt = aulitscnt;
+                _result.xrep = xrep;
+                _result.stpmax = stpmax;
+                _result.diffstep = diffstep;
+                _result.teststep = teststep;
+                _result.s = (double[])s.Clone();
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.hasbndl = (bool[])hasbndl.Clone();
+                _result.hasbndu = (bool[])hasbndu.Clone();
+                _result.nec = nec;
+                _result.nic = nic;
+                _result.cleic = (double[,])cleic.Clone();
+                _result.lcsrcidx = (int[])lcsrcidx.Clone();
+                _result.ng = ng;
+                _result.nh = nh;
+                _result.x = (double[])x.Clone();
+                _result.f = f;
+                _result.fi = (double[])fi.Clone();
+                _result.j = (double[,])j.Clone();
+                _result.needfij = needfij;
+                _result.needfi = needfi;
+                _result.xupdated = xupdated;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.rstateaul = (rcommstate)rstateaul.make_copy();
+                _result.rstateslp = (rcommstate)rstateslp.make_copy();
+                _result.scaledbndl = (double[])scaledbndl.Clone();
+                _result.scaledbndu = (double[])scaledbndu.Clone();
+                _result.scaledcleic = (double[,])scaledcleic.Clone();
+                _result.xc = (double[])xc.Clone();
+                _result.xstart = (double[])xstart.Clone();
+                _result.xbase = (double[])xbase.Clone();
+                _result.fbase = (double[])fbase.Clone();
+                _result.dfbase = (double[])dfbase.Clone();
+                _result.fm2 = (double[])fm2.Clone();
+                _result.fm1 = (double[])fm1.Clone();
+                _result.fp1 = (double[])fp1.Clone();
+                _result.fp2 = (double[])fp2.Clone();
+                _result.dfm1 = (double[])dfm1.Clone();
+                _result.dfp1 = (double[])dfp1.Clone();
+                _result.bufd = (double[])bufd.Clone();
+                _result.bufc = (double[])bufc.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.bufw = (double[,])bufw.Clone();
+                _result.bufz = (double[,])bufz.Clone();
+                _result.xk = (double[])xk.Clone();
+                _result.xk1 = (double[])xk1.Clone();
+                _result.gk = (double[])gk.Clone();
+                _result.gk1 = (double[])gk1.Clone();
+                _result.gammak = gammak;
+                _result.xkpresent = xkpresent;
+                _result.auloptimizer = (minlbfgs.minlbfgsstate)auloptimizer.make_copy();
+                _result.aulreport = (minlbfgs.minlbfgsreport)aulreport.make_copy();
+                _result.nubc = (double[])nubc.Clone();
+                _result.nulc = (double[])nulc.Clone();
+                _result.nunlc = (double[])nunlc.Clone();
+                _result.userterminationneeded = userterminationneeded;
+                _result.slpsolverstate = (nlcslp.minslpstate)slpsolverstate.make_copy();
+                _result.smoothnessguardlevel = smoothnessguardlevel;
+                _result.smonitor = (optserv.smoothnessmonitor)smonitor.make_copy();
+                _result.lastscaleused = (double[])lastscaleused.Clone();
+                _result.repinneriterationscount = repinneriterationscount;
+                _result.repouteriterationscount = repouteriterationscount;
+                _result.repnfev = repnfev;
+                _result.repterminationtype = repterminationtype;
+                _result.repbcerr = repbcerr;
+                _result.repbcidx = repbcidx;
+                _result.replcerr = replcerr;
+                _result.replcidx = replcidx;
+                _result.repnlcerr = repnlcerr;
+                _result.repnlcidx = repnlcidx;
+                _result.repdbgphase0its = repdbgphase0its;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        These fields store optimization report:
+        * iterationscount           total number of inner iterations
+        * nfev                      number of gradient evaluations
+        * terminationtype           termination type (see below)
+
+        Scaled constraint violations are reported:
+        * bcerr                     maximum violation of the box constraints
+        * bcidx                     index of the most violated box  constraint (or
+                                    -1, if all box constraints  are  satisfied  or
+                                    there is no box constraint)
+        * lcerr                     maximum violation of the  linear  constraints,
+                                    computed as maximum  scaled  distance  between
+                                    final point and constraint boundary.
+        * lcidx                     index of the most violated  linear  constraint
+                                    (or -1, if all constraints  are  satisfied  or
+                                    there is no general linear constraints)
+        * nlcerr                    maximum violation of the nonlinear constraints
+        * nlcidx                    index of the most violated nonlinear constraint
+                                    (or -1, if all constraints  are  satisfied  or
+                                    there is no nonlinear constraints)
+                                    
+        Violations of box constraints are scaled on per-component basis  according 
+        to  the  scale  vector s[] as specified by minnlcsetscale(). Violations of
+        the general linear  constraints  are  also  computed  using  user-supplied
+        variable scaling. Violations of nonlinear constraints are computed "as is"
+
+        TERMINATION CODES
+
+        TerminationType field contains completion code, which can be either:
+
+        === FAILURE CODE ===
+          -8    internal integrity control detected  infinite  or  NAN  values  in
+                function/gradient. Abnormal termination signaled.
+          -3    box  constraints  are  infeasible.  Note: infeasibility of non-box
+                constraints does NOT trigger emergency  completion;  you  have  to
+                examine  bcerr/lcerr/nlcerr   to  detect   possibly   inconsistent
+                constraints.
+
+        === SUCCESS CODE ===
+           2    relative step is no more than EpsX.
+           5    MaxIts steps was taken
+           7    stopping conditions are too stringent,
+                further improvement is impossible,
+                X contains best point found so far.
+           8    user requested algorithm termination via minnlcrequesttermination(),
+                last accepted point is returned
+                
+        Other fields of this structure are not documented and should not be used!
+        *************************************************************************/
+        public class minnlcreport : apobject
+        {
+            public int iterationscount;
+            public int nfev;
+            public int terminationtype;
+            public double bcerr;
+            public int bcidx;
+            public double lcerr;
+            public int lcidx;
+            public double nlcerr;
+            public int nlcidx;
+            public int dbgphase0its;
+            public minnlcreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                minnlcreport _result = new minnlcreport();
+                _result.iterationscount = iterationscount;
+                _result.nfev = nfev;
+                _result.terminationtype = terminationtype;
+                _result.bcerr = bcerr;
+                _result.bcidx = bcidx;
+                _result.lcerr = lcerr;
+                _result.lcidx = lcidx;
+                _result.nlcerr = nlcerr;
+                _result.nlcidx = nlcidx;
+                _result.dbgphase0its = dbgphase0its;
+                return _result;
+            }
+        };
+
+
+
+
+        public const double aulmaxgrowth = 10.0;
+        public const double maxlagmult = 1.0E7;
+        public const int lbfgsfactor = 10;
+        public const double hessesttol = 1.0E-6;
+        public const double initgamma = 1.0E-6;
+        public const double regprec = 1.0E-6;
+
+
+        /*************************************************************************
+                          NONLINEARLY  CONSTRAINED  OPTIMIZATION
+                    WITH PRECONDITIONED AUGMENTED LAGRANGIAN ALGORITHM
+
+        DESCRIPTION:
+        The  subroutine  minimizes  function   F(x)  of N arguments subject to any
+        combination of:
+        * bound constraints
+        * linear inequality constraints
+        * linear equality constraints
+        * nonlinear equality constraints Gi(x)=0
+        * nonlinear inequality constraints Hi(x)<=0
+
+        REQUIREMENTS:
+        * user must provide function value and gradient for F(), H(), G()
+        * starting point X0 must be feasible or not too far away from the feasible
+          set
+        * F(), G(), H() are twice continuously differentiable on the feasible  set
+          and its neighborhood
+        * nonlinear constraints G() and H() must have non-zero gradient at  G(x)=0
+          and at H(x)=0. Say, constraint like x^2>=1 is supported, but x^2>=0   is
+          NOT supported.
+
+        USAGE:
+
+        Constrained optimization if far more complex than the  unconstrained  one.
+        Nonlinearly constrained optimization is one of the most esoteric numerical
+        procedures.
+
+        Here we give very brief outline  of  the  MinNLC  optimizer.  We  strongly
+        recommend you to study 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 MinNLCCreate() call  and  chooses
+           what NLC solver to use. There is some solver which is used by  default,
+           with default settings, but you should NOT rely on  default  choice.  It
+           may change in future releases of ALGLIB without notice, and no one  can
+           guarantee that new solver will be  able  to  solve  your  problem  with
+           default settings.
+           
+           From the other side, if you choose solver explicitly, you can be pretty
+           sure that it will work with new ALGLIB releases.
+           
+           In the current release following solvers can be used:
+           * SLP solver (activated with minnlcsetalgoslp() function) -  successive
+             linear programming, recommended as the first step.
+           * AUL solver (activated with minnlcsetalgoaul() function)  -  augmented
+             Lagrangian method with dense preconditioner.
+         
+           SLP solver is the most robust one  in  ALGLIB  and  converges  in  less
+           iterations than AUL; however, each iteration has higher overhead  -  we
+           have to solve an LP problem. From  the  other  side,  AUL  has  cheaper
+           iterations - although it typically needs more of them, and also  it  is
+           less robust in nonconvex setting.
+
+        2. [optional] user activates OptGuard  integrity checker  which  tries  to
+           detect possible errors in the user-supplied callbacks:
+           * discontinuity/nonsmoothness of the target/nonlinear constraints
+           * errors in the analytic gradient provided by user
+           This feature is essential for early prototyping stages because it helps
+           to catch common coding and problem statement errors.
+           OptGuard can be activated with following functions (one per each  check
+           performed):
+           * minnlcoptguardsmoothness()
+           * minnlcoptguardgradient()
+
+        3. User adds boundary and/or linear and/or nonlinear constraints by  means
+           of calling one of the following functions:
+           a) minnlcsetbc() for boundary constraints
+           b) minnlcsetlc() for linear constraints
+           c) minnlcsetnlc() for nonlinear constraints
+           You may combine (a), (b) and (c) in one optimization problem.
+           
+        4. User sets scale of the variables with minnlcsetscale() function. It  is
+           VERY important to set  scale  of  the  variables,  because  nonlinearly
+           constrained problems are hard to solve when variables are badly scaled.
+
+        5. User sets  stopping  conditions  with  minnlcsetcond(). If  NLC  solver
+           uses  inner/outer  iteration  layout,  this  function   sets   stopping
+           conditions for INNER iterations.
+           
+        6. Finally, user calls minnlcoptimize()  function  which  takes  algorithm
+           state and pointer (delegate, etc.) to callback function which calculates
+           F/G/H.
+
+        7. User calls  minnlcresults()  to  get  solution;  additionally  you  can
+           retrieve OptGuard report with minnlcoptguardresults(), and get detailed
+           report about purported errors in the target function with:
+           * minnlcoptguardnonc1test0results()
+           * minnlcoptguardnonc1test1results()
+
+        8. Optionally user may call minnlcrestartfrom() to solve  another  problem
+           with same N but another starting point. minnlcrestartfrom()  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 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlccreate(int n,
+            double[] x,
+            minnlcstate state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(n>=1, "MinNLCCreate: N<1");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinNLCCreate: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "MinNLCCreate: X contains infinite or NaN values");
+            minnlcinitinternal(n, x, 0.0, state, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine is a finite  difference variant of MinNLCCreate(). 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 MinNLCCreate() in order to get more
+        information about creation of NLC 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 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.
+            DiffStep-   differentiation step, >0
+
+        OUTPUT PARAMETERS:
+            State   -   structure 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 MinNLCSetScale() 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-4  can  be  good
+           value to start from.
+        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.  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 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlccreatef(int n,
+            double[] x,
+            double diffstep,
+            minnlcstate state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(n>=1, "MinNLCCreateF: N<1");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinNLCCreateF: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "MinNLCCreateF: X contains infinite or NaN values");
+            alglib.ap.assert(math.isfinite(diffstep), "MinNLCCreateF: DiffStep is infinite or NaN!");
+            alglib.ap.assert((double)(diffstep)>(double)(0), "MinNLCCreateF: DiffStep is non-positive!");
+            minnlcinitinternal(n, x, diffstep, state, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets boundary constraints for NLC optimizer.
+
+        Boundary constraints are inactive by  default  (after  initial  creation).
+        They are preserved after algorithm restart with  MinNLCRestartFrom().
+
+        You may combine boundary constraints with  general  linear ones - and with
+        nonlinear ones! Boundary constraints are  handled  more  efficiently  than
+        other types.  Thus,  if  your  problem  has  mixed  constraints,  you  may
+        explicitly specify some of them as boundary and save some time/space.
+
+        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:  when you solve your problem  with  augmented  Lagrangian  solver,
+                 boundary constraints are  satisfied  only  approximately!  It  is
+                 possible   that  algorithm  will  evaluate  function  outside  of
+                 feasible area!
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetbc(minnlcstate state,
+            double[] bndl,
+            double[] bndu,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = state.n;
+            alglib.ap.assert(alglib.ap.len(bndl)>=n, "MinNLCSetBC: Length(BndL)<N");
+            alglib.ap.assert(alglib.ap.len(bndu)>=n, "MinNLCSetBC: Length(BndU)<N");
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(bndl[i]) || Double.IsNegativeInfinity(bndl[i]), "MinNLCSetBC: BndL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(bndu[i]) || Double.IsPositiveInfinity(bndu[i]), "MinNLCSetBC: BndL contains NAN or -INF");
+                state.bndl[i] = bndl[i];
+                state.hasbndl[i] = math.isfinite(bndl[i]);
+                state.bndu[i] = bndu[i];
+                state.hasbndu[i] = math.isfinite(bndu[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets linear constraints for MinNLC optimizer.
+
+        Linear constraints are inactive by default (after initial creation).  They
+        are preserved after algorithm restart with MinNLCRestartFrom().
+
+        You may combine linear constraints with boundary ones - and with nonlinear
+        ones! If your problem has mixed constraints, you  may  explicitly  specify
+        some of them as linear. It  may  help  optimizer   to   handle  them  more
+        efficiently.
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with MinNLCCreate 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: when you solve your problem  with  augmented  Lagrangian   solver,
+                linear constraints are  satisfied  only   approximately!   It   is
+                possible   that  algorithm  will  evaluate  function  outside   of
+                feasible area!
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetlc(minnlcstate state,
+            double[,] c,
+            int[] ct,
+            int k,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int i_ = 0;
+
+            n = state.n;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(k>=0, "MinNLCSetLC: K<0");
+            alglib.ap.assert(alglib.ap.cols(c)>=n+1 || k==0, "MinNLCSetLC: Cols(C)<N+1");
+            alglib.ap.assert(alglib.ap.rows(c)>=k, "MinNLCSetLC: Rows(C)<K");
+            alglib.ap.assert(alglib.ap.len(ct)>=k, "MinNLCSetLC: Length(CT)<K");
+            alglib.ap.assert(apserv.apservisfinitematrix(c, k, n+1, _params), "MinNLCSetLC: C contains infinite or NaN values!");
+            
+            //
+            // Handle zero K
+            //
+            if( k==0 )
+            {
+                state.nec = 0;
+                state.nic = 0;
+                return;
+            }
+            
+            //
+            // Equality constraints are stored first, in the upper
+            // NEC rows of State.CLEIC matrix. Inequality constraints
+            // are stored in the next NIC rows.
+            //
+            // NOTE: we convert inequality constraints to the form
+            // A*x<=b before copying them.
+            //
+            apserv.rmatrixsetlengthatleast(ref state.cleic, k, n+1, _params);
+            apserv.ivectorsetlengthatleast(ref state.lcsrcidx, k, _params);
+            state.nec = 0;
+            state.nic = 0;
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]==0 )
+                {
+                    for(i_=0; i_<=n;i_++)
+                    {
+                        state.cleic[state.nec,i_] = c[i,i_];
+                    }
+                    state.lcsrcidx[state.nec] = i;
+                    state.nec = state.nec+1;
+                }
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]!=0 )
+                {
+                    if( ct[i]>0 )
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec+state.nic,i_] = -c[i,i_];
+                        }
+                    }
+                    else
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec+state.nic,i_] = c[i,i_];
+                        }
+                    }
+                    state.lcsrcidx[state.nec+state.nic] = i;
+                    state.nic = state.nic+1;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets nonlinear constraints for MinNLC optimizer.
+
+        In fact, this function sets NUMBER of nonlinear  constraints.  Constraints
+        itself (constraint functions) are passed to MinNLCOptimize() method.  This
+        method requires user-defined vector function F[]  and  its  Jacobian  J[],
+        where:
+        * first component of F[] and first row  of  Jacobian  J[]  corresponds  to
+          function being minimized
+        * next NLEC components of F[] (and rows  of  J)  correspond  to  nonlinear
+          equality constraints G_i(x)=0
+        * next NLIC components of F[] (and rows  of  J)  correspond  to  nonlinear
+          inequality constraints H_i(x)<=0
+
+        NOTE: you may combine nonlinear constraints with linear/boundary ones.  If
+              your problem has mixed constraints, you  may explicitly specify some
+              of them as linear ones. It may help optimizer to  handle  them  more
+              efficiently.
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with MinNLCCreate call.
+            NLEC    -   number of Non-Linear Equality Constraints (NLEC), >=0
+            NLIC    -   number of Non-Linear Inquality Constraints (NLIC), >=0
+
+        NOTE 1: when you solve your problem  with  augmented  Lagrangian   solver,
+                nonlinear constraints are satisfied only  approximately!   It   is
+                possible   that  algorithm  will  evaluate  function  outside   of
+                feasible area!
+                
+        NOTE 2: algorithm scales variables  according  to   scale   specified   by
+                MinNLCSetScale()  function,  so  it can handle problems with badly
+                scaled variables (as long as we KNOW their scales).
+                   
+                However,  there  is  no  way  to  automatically  scale   nonlinear
+                constraints Gi(x) and Hi(x). Inappropriate scaling  of  Gi/Hi  may
+                ruin convergence. Solving problem with  constraint  "1000*G0(x)=0"
+                is NOT same as solving it with constraint "0.001*G0(x)=0".
+                   
+                It  means  that  YOU  are  the  one who is responsible for correct
+                scaling of nonlinear constraints Gi(x) and Hi(x). We recommend you
+                to scale nonlinear constraints in such way that I-th component  of
+                dG/dX (or dH/dx) has approximately unit  magnitude  (for  problems
+                with unit scale)  or  has  magnitude approximately equal to 1/S[i]
+                (where S is a scale set by MinNLCSetScale() function).
+
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetnlc(minnlcstate state,
+            int nlec,
+            int nlic,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(nlec>=0, "MinNLCSetNLC: NLEC<0");
+            alglib.ap.assert(nlic>=0, "MinNLCSetNLC: NLIC<0");
+            state.ng = nlec;
+            state.nh = nlic;
+            state.fi = new double[1+state.ng+state.nh];
+            state.j = new double[1+state.ng+state.nh, state.n];
+        }
+
+
+        /*************************************************************************
+        This function sets stopping conditions for inner iterations of  optimizer.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            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 - step vector, dx=X(k+1)-X(k)
+                        * s - scaling coefficients set by MinNLCSetScale()
+            MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                        iterations is unlimited.
+
+        Passing EpsX=0 and MaxIts=0 (simultaneously) will lead to automatic
+        selection of the stopping condition.
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetcond(minnlcstate state,
+            double epsx,
+            int maxits,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(epsx), "MinNLCSetCond: EpsX is not finite number");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "MinNLCSetCond: negative EpsX");
+            alglib.ap.assert(maxits>=0, "MinNLCSetCond: negative MaxIts!");
+            if( (double)(epsx)==(double)(0) && maxits==0 )
+            {
+                epsx = 1.0E-8;
+            }
+            state.epsx = epsx;
+            state.maxits = maxits;
+        }
+
+
+        /*************************************************************************
+        This function sets scaling coefficients for NLC 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].
+
+        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 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetscale(minnlcstate state,
+            double[] s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(s)>=state.n, "MinNLCSetScale: Length(S)<N");
+            for(i=0; i<=state.n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(s[i]), "MinNLCSetScale: S contains infinite or NAN elements");
+                alglib.ap.assert((double)(s[i])!=(double)(0), "MinNLCSetScale: S contains zero elements");
+                state.s[i] = Math.Abs(s[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets preconditioner to "inexact LBFGS-based" mode.
+
+        Preconditioning is very important for convergence of  Augmented Lagrangian
+        algorithm because presence of penalty term makes problem  ill-conditioned.
+        Difference between  performance  of  preconditioned  and  unpreconditioned
+        methods can be as large as 100x!
+
+        MinNLC optimizer may use following preconditioners,  each  with   its  own
+        benefits and drawbacks:
+            a) inexact LBFGS-based, with O(N*K) evaluation time
+            b) exact low rank one,  with O(N*K^2) evaluation time
+            c) exact robust one,    with O(N^3+K*N^2) evaluation time
+        where K is a total number of general linear and nonlinear constraints (box
+        ones are not counted).
+
+        Inexact  LBFGS-based  preconditioner  uses L-BFGS  formula  combined  with
+        orthogonality assumption to perform very fast updates. For a N-dimensional
+        problem with K general linear or nonlinear constraints (boundary ones  are
+        not counted) it has O(N*K) cost per iteration.  This   preconditioner  has
+        best  quality  (less  iterations)  when   general   linear  and  nonlinear
+        constraints are orthogonal to each other (orthogonality  with  respect  to
+        boundary constraints is not required). Number of iterations increases when
+        constraints  are  non-orthogonal, because algorithm assumes orthogonality,
+        but still it is better than no preconditioner at all.
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+
+          -- ALGLIB --
+             Copyright 26.09.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetprecinexact(minnlcstate state,
+            alglib.xparams _params)
+        {
+            state.updatefreq = 0;
+            state.prectype = 1;
+        }
+
+
+        /*************************************************************************
+        This function sets preconditioner to "exact low rank" mode.
+
+        Preconditioning is very important for convergence of  Augmented Lagrangian
+        algorithm because presence of penalty term makes problem  ill-conditioned.
+        Difference between  performance  of  preconditioned  and  unpreconditioned
+        methods can be as large as 100x!
+
+        MinNLC optimizer may use following preconditioners,  each  with   its  own
+        benefits and drawbacks:
+            a) inexact LBFGS-based, with O(N*K) evaluation time
+            b) exact low rank one,  with O(N*K^2) evaluation time
+            c) exact robust one,    with O(N^3+K*N^2) evaluation time
+        where K is a total number of general linear and nonlinear constraints (box
+        ones are not counted).
+
+        It also provides special unpreconditioned mode of operation which  can  be
+        used for test purposes. Comments below discuss low rank preconditioner.
+
+        Exact low-rank preconditioner  uses  Woodbury  matrix  identity  to  build
+        quadratic model of the penalized function. It has following features:
+        * no special assumptions about orthogonality of constraints
+        * preconditioner evaluation is optimized for K<<N. Its cost  is  O(N*K^2),
+          so it may become prohibitively slow for K>=N.
+        * finally, stability of the process is guaranteed only for K<<N.  Woodbury
+          update often fail for K>=N due to degeneracy of  intermediate  matrices.
+          That's why we recommend to use "exact robust"  preconditioner  for  such
+          cases.
+
+        RECOMMENDATIONS
+
+        We  recommend  to  choose  between  "exact  low  rank"  and "exact robust"
+        preconditioners, with "low rank" version being chosen  when  you  know  in
+        advance that total count of non-box constraints won't exceed N, and "robust"
+        version being chosen when you need bulletproof solution.
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            UpdateFreq- update frequency. Preconditioner is  rebuilt  after  every
+                        UpdateFreq iterations. Recommended value: 10 or higher.
+                        Zero value means that good default value will be used.
+
+          -- ALGLIB --
+             Copyright 26.09.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetprecexactlowrank(minnlcstate state,
+            int updatefreq,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(updatefreq>=0, "MinNLCSetPrecExactLowRank: UpdateFreq<0");
+            if( updatefreq==0 )
+            {
+                updatefreq = 10;
+            }
+            state.prectype = 2;
+            state.updatefreq = updatefreq;
+        }
+
+
+        /*************************************************************************
+        This function sets preconditioner to "exact robust" mode.
+
+        Preconditioning is very important for convergence of  Augmented Lagrangian
+        algorithm because presence of penalty term makes problem  ill-conditioned.
+        Difference between  performance  of  preconditioned  and  unpreconditioned
+        methods can be as large as 100x!
+
+        MinNLC optimizer may use following preconditioners,  each  with   its  own
+        benefits and drawbacks:
+            a) inexact LBFGS-based, with O(N*K) evaluation time
+            b) exact low rank one,  with O(N*K^2) evaluation time
+            c) exact robust one,    with O(N^3+K*N^2) evaluation time
+        where K is a total number of general linear and nonlinear constraints (box
+        ones are not counted).
+
+        It also provides special unpreconditioned mode of operation which  can  be
+        used for test purposes. Comments below discuss robust preconditioner.
+
+        Exact  robust  preconditioner   uses   Cholesky  decomposition  to  invert
+        approximate Hessian matrix H=D+W'*C*W (where D stands for  diagonal  terms
+        of Hessian, combined result of initial scaling matrix and penalty from box
+        constraints; W stands for general linear constraints and linearization  of
+        nonlinear ones; C stands for diagonal matrix of penalty coefficients).
+
+        This preconditioner has following features:
+        * no special assumptions about constraint structure
+        * preconditioner is optimized  for  stability;  unlike  "exact  low  rank"
+          version which fails for K>=N, this one works well for any value of K.
+        * the only drawback is that is takes O(N^3+K*N^2) time  to  build  it.  No
+          economical  Woodbury update is applied even when it  makes  sense,  thus
+          there  are  exist situations (K<<N) when "exact low rank" preconditioner
+          outperforms this one.
+          
+        RECOMMENDATIONS
+
+        We  recommend  to  choose  between  "exact  low  rank"  and "exact robust"
+        preconditioners, with "low rank" version being chosen  when  you  know  in
+        advance that total count of non-box constraints won't exceed N, and "robust"
+        version being chosen when you need bulletproof solution.
+          
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            UpdateFreq- update frequency. Preconditioner is  rebuilt  after  every
+                        UpdateFreq iterations. Recommended value: 10 or higher.
+                        Zero value means that good default value will be used.
+
+          -- ALGLIB --
+             Copyright 26.09.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetprecexactrobust(minnlcstate state,
+            int updatefreq,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(updatefreq>=0, "MinNLCSetPrecExactLowRank: UpdateFreq<0");
+            if( updatefreq==0 )
+            {
+                updatefreq = 10;
+            }
+            state.prectype = 3;
+            state.updatefreq = updatefreq;
+        }
+
+
+        /*************************************************************************
+        This function sets preconditioner to "turned off" mode.
+
+        Preconditioning is very important for convergence of  Augmented Lagrangian
+        algorithm because presence of penalty term makes problem  ill-conditioned.
+        Difference between  performance  of  preconditioned  and  unpreconditioned
+        methods can be as large as 100x!
+
+        MinNLC optimizer may  utilize  two  preconditioners,  each  with  its  own
+        benefits and drawbacks: a) inexact LBFGS-based, and b) exact low rank one.
+        It also provides special unpreconditioned mode of operation which  can  be
+        used for test purposes.
+
+        This function activates this test mode. Do not use it in  production  code
+        to solve real-life problems.
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+
+          -- ALGLIB --
+             Copyright 26.09.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetprecnone(minnlcstate state,
+            alglib.xparams _params)
+        {
+            state.updatefreq = 0;
+            state.prectype = 0;
+        }
+
+
+        /*************************************************************************
+        This function sets maximum step length (after scaling of step vector  with
+        respect to variable scales specified by minnlcsetscale() call).
+
+        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.
+
+        NOTE: different solvers employed by MinNLC optimizer use  different  norms
+              for step; AUL solver uses 2-norm, whilst SLP solver uses INF-norm.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetstpmax(minnlcstate state,
+            double stpmax,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(stpmax), "MinNLCSetStpMax: StpMax is not finite!");
+            alglib.ap.assert((double)(stpmax)>=(double)(0), "MinNLCSetStpMax: StpMax<0!");
+            state.stpmax = stpmax;
+        }
+
+
+        /*************************************************************************
+        This  function  tells MinNLC unit to use  Augmented  Lagrangian  algorithm
+        for nonlinearly constrained  optimization.  This  algorithm  is  a  slight
+        modification of one described in "A Modified Barrier-Augmented  Lagrangian
+        Method for  Constrained  Minimization  (1999)"  by  D.GOLDFARB,  R.POLYAK,
+        K. SCHEINBERG, I.YUZEFOVICH.
+
+        AUL solver can be significantly faster than SLP on easy problems, although
+        it is less robust than SLP (the "gold standard" of robust optimization).
+
+        Augmented Lagrangian algorithm works by converting problem  of  minimizing
+        F(x) subject to equality/inequality constraints   to unconstrained problem
+        of the form
+
+            min[ f(x) + 
+                + Rho*PENALTY_EQ(x)   + SHIFT_EQ(x,Nu1) + 
+                + Rho*PENALTY_INEQ(x) + SHIFT_INEQ(x,Nu2) ]
+            
+        where:
+        * Rho is a fixed penalization coefficient
+        * PENALTY_EQ(x) is a penalty term, which is used to APPROXIMATELY  enforce
+          equality constraints
+        * SHIFT_EQ(x) is a special "shift"  term  which  is  used  to  "fine-tune"
+          equality constraints, greatly increasing precision
+        * PENALTY_INEQ(x) is a penalty term which is used to approximately enforce
+          inequality constraints
+        * SHIFT_INEQ(x) is a special "shift"  term  which  is  used to "fine-tune"
+          inequality constraints, greatly increasing precision
+        * Nu1/Nu2 are vectors of Lagrange coefficients which are fine-tuned during
+          outer iterations of algorithm
+
+        This  version  of  AUL  algorithm  uses   preconditioner,  which   greatly
+        accelerates convergence. Because this  algorithm  is  similar  to  penalty
+        methods,  it  may  perform  steps  into  infeasible  area.  All  kinds  of
+        constraints (boundary, linear and nonlinear ones) may   be   violated   in
+        intermediate points - and in the solution.  However,  properly  configured
+        AUL method is significantly better at handling  constraints  than  barrier
+        and/or penalty methods.
+
+        The very basic outline of algorithm is given below:
+        1) first outer iteration is performed with "default"  values  of  Lagrange
+           multipliers Nu1/Nu2. Solution quality is low (candidate  point  can  be
+           too  far  away  from  true  solution; large violation of constraints is
+           possible) and is comparable with that of penalty methods.
+        2) subsequent outer iterations  refine  Lagrange  multipliers  and improve
+           quality of the solution.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            Rho     -   penalty coefficient, Rho>0:
+                        * large enough  that  algorithm  converges  with   desired
+                          precision. Minimum value is 10*max(S'*diag(H)*S),  where
+                          S is a scale matrix (set by MinNLCSetScale) and H  is  a
+                          Hessian of the function being minimized. If you can  not
+                          easily estimate Hessian norm,  see  our  recommendations
+                          below.
+                        * not TOO large to prevent ill-conditioning
+                        * for unit-scale problems (variables and Hessian have unit
+                          magnitude), Rho=100 or Rho=1000 can be used.
+                        * it is important to note that Rho is internally multiplied
+                          by scaling matrix, i.e. optimum value of Rho depends  on
+                          scale of variables specified  by  MinNLCSetScale().
+            ItsCnt  -   number of outer iterations:
+                        * ItsCnt=0 means that small number of outer iterations  is
+                          automatically chosen (10 iterations in current version).
+                        * ItsCnt=1 means that AUL algorithm performs just as usual
+                          barrier method.
+                        * ItsCnt>1 means that  AUL  algorithm  performs  specified
+                          number of outer iterations
+                        
+        HOW TO CHOOSE PARAMETERS
+
+        Nonlinear optimization is a tricky area and Augmented Lagrangian algorithm
+        is sometimes hard to tune. Good values of  Rho  and  ItsCnt  are  problem-
+        specific.  In  order  to  help  you   we   prepared   following   set   of
+        recommendations:
+
+        * for  unit-scale  problems  (variables  and Hessian have unit magnitude),
+          Rho=100 or Rho=1000 can be used.
+
+        * start from  some  small  value of Rho and solve problem  with  just  one
+          outer iteration (ItcCnt=1). In this case algorithm behaves like  penalty
+          method. Increase Rho in 2x or 10x steps until you  see  that  one  outer
+          iteration returns point which is "rough approximation to solution".
+          
+          It is very important to have Rho so  large  that  penalty  term  becomes
+          constraining i.e. modified function becomes highly convex in constrained
+          directions.
+          
+          From the other side, too large Rho may prevent you  from  converging  to
+          the solution. You can diagnose it by studying number of inner iterations
+          performed by algorithm: too few (5-10 on  1000-dimensional  problem)  or
+          too many (orders of magnitude more than  dimensionality)  usually  means
+          that Rho is too large.
+
+        * with just one outer iteration you  usually  have  low-quality  solution.
+          Some constraints can be violated with very  large  margin,  while  other
+          ones (which are NOT violated in the true solution) can push final  point
+          too far in the inner area of the feasible set.
+          
+          For example, if you have constraint x0>=0 and true solution  x0=1,  then
+          merely a presence of "x0>=0" will introduce a bias towards larger values
+          of x0. Say, algorithm may stop at x0=1.5 instead of 1.0.
+          
+        * after you found good Rho, you may increase number of  outer  iterations.
+          ItsCnt=10 is a good value. Subsequent outer iteration will refine values
+          of  Lagrange  multipliers.  Constraints  which  were  violated  will  be
+          enforced, inactive constraints will be dropped (corresponding multipliers
+          will be decreased). Ideally, you  should  see  10-1000x  improvement  in
+          constraint handling (constraint violation is reduced).
+          
+        * if  you  see  that  algorithm  converges  to  vicinity  of solution, but
+          additional outer iterations do not refine solution,  it  may  mean  that
+          algorithm is unstable - it wanders around true  solution,  but  can  not
+          approach it. Sometimes algorithm may be stabilized by increasing Rho one
+          more time, making it 5x or 10x larger.
+
+        SCALING OF CONSTRAINTS [IMPORTANT]
+
+        AUL optimizer scales   variables   according   to   scale   specified   by
+        MinNLCSetScale() function, so it can handle  problems  with  badly  scaled
+        variables (as long as we KNOW their scales).   However,  because  function
+        being optimized is a mix  of  original  function and  constraint-dependent
+        penalty  functions, it  is   important  to   rescale  both  variables  AND
+        constraints.
+
+        Say,  if  you  minimize f(x)=x^2 subject to 1000000*x>=0,  then  you  have
+        constraint whose scale is different from that of target  function (another
+        example is 0.000001*x>=0). It is also possible to have constraints   whose
+        scales  are   misaligned:   1000000*x0>=0, 0.000001*x1<=0.   Inappropriate
+        scaling may ruin convergence because minimizing x^2 subject to x>=0 is NOT
+        same as minimizing it subject to 1000000*x>=0.
+
+        Because we  know  coefficients  of  boundary/linear  constraints,  we  can
+        automatically rescale and normalize them. However,  there  is  no  way  to
+        automatically rescale nonlinear constraints Gi(x) and  Hi(x)  -  they  are
+        black boxes.
+
+        It means that YOU are the one who is  responsible  for  correct scaling of
+        nonlinear constraints  Gi(x)  and  Hi(x).  We  recommend  you  to  rescale
+        nonlinear constraints in such way that I-th component of dG/dX (or  dH/dx)
+        has magnitude approximately equal to 1/S[i] (where S  is  a  scale  set by
+        MinNLCSetScale() function).
+
+        WHAT IF IT DOES NOT CONVERGE?
+
+        It is possible that AUL algorithm fails to converge to precise  values  of
+        Lagrange multipliers. It stops somewhere around true solution, but candidate
+        point is still too far from solution, and some constraints  are  violated.
+        Such kind of failure is specific for Lagrangian algorithms -  technically,
+        they stop at some point, but this point is not constrained solution.
+
+        There are exist several reasons why algorithm may fail to converge:
+        a) too loose stopping criteria for inner iteration
+        b) degenerate, redundant constraints
+        c) target function has unconstrained extremum exactly at the  boundary  of
+           some constraint
+        d) numerical noise in the target function
+
+        In all these cases algorithm is unstable - each outer iteration results in
+        large and almost random step which improves handling of some  constraints,
+        but violates other ones (ideally  outer iterations should form a  sequence
+        of progressively decreasing steps towards solution).
+           
+        First reason possible is  that  too  loose  stopping  criteria  for  inner
+        iteration were specified. Augmented Lagrangian algorithm solves a sequence
+        of intermediate problems, and requries each of them to be solved with high
+        precision. Insufficient precision results in incorrect update of  Lagrange
+        multipliers.
+
+        Another reason is that you may have specified degenerate constraints: say,
+        some constraint was repeated twice. In most cases AUL algorithm gracefully
+        handles such situations, but sometimes it may spend too much time figuring
+        out subtle degeneracies in constraint matrix.
+
+        Third reason is tricky and hard to diagnose. Consider situation  when  you
+        minimize  f=x^2  subject to constraint x>=0.  Unconstrained   extremum  is
+        located  exactly  at  the  boundary  of  constrained  area.  In  this case
+        algorithm will tend to oscillate between negative  and  positive  x.  Each
+        time it stops at x<0 it "reinforces" constraint x>=0, and each time it  is
+        bounced to x>0 it "relaxes" constraint (and is  attracted  to  x<0).
+
+        Such situation  sometimes  happens  in  problems  with  hidden  symetries.
+        Algorithm  is  got  caught  in  a  loop with  Lagrange  multipliers  being
+        continuously increased/decreased. Luckily, such loop forms after at  least
+        three iterations, so this problem can be solved by  DECREASING  number  of
+        outer iterations down to 1-2 and increasing  penalty  coefficient  Rho  as
+        much as possible.
+
+        Final reason is numerical noise. AUL algorithm is robust against  moderate
+        noise (more robust than, say, active set methods),  but  large  noise  may
+        destabilize algorithm.
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetalgoaul(minnlcstate state,
+            double rho,
+            int itscnt,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(itscnt>=0, "MinNLCSetAlgoAUL: negative ItsCnt");
+            alglib.ap.assert(math.isfinite(rho), "MinNLCSetAlgoAUL: Rho is not finite");
+            alglib.ap.assert((double)(rho)>(double)(0), "MinNLCSetAlgoAUL: Rho<=0");
+            if( itscnt==0 )
+            {
+                itscnt = 10;
+            }
+            state.aulitscnt = itscnt;
+            state.rho = rho;
+            state.solvertype = 0;
+        }
+
+
+        /*************************************************************************
+        This   function  tells  MinNLC  optimizer  to  use  SLP (Successive Linear
+        Programming) algorithm for  nonlinearly  constrained   optimization.  This
+        algorithm  is  a  slight  modification  of  one  described  in  "A  Linear
+        programming-based optimization algorithm for solving nonlinear programming
+        problems" (2010) by Claus Still and Tapio Westerlund.
+
+        Despite its name ("linear" = "first order method") this algorithm performs
+        steps similar to that of conjugate gradients method;  internally  it  uses
+        orthogonality/conjugacy requirement for subsequent steps  which  makes  it
+        closer to second order methods in terms of convergence speed.
+
+        Convergence is proved for the following case:
+        * function and constraints are continuously differentiable (C1 class)
+        * extended MangasarianâFromovitz constraint qualification  (EMFCQ)  holds;
+          in the context of this algorithm EMFCQ  means  that  one  can,  for  any
+          infeasible  point,  find  a  search  direction  such that the constraint
+          infeasibilities are reduced.
+
+        This algorithm has following nice properties:
+        * no parameters to tune
+        * no convexity requirements for target function or constraints
+        * initial point can be infeasible
+        * algorithm respects box constraints in all intermediate points  (it  does
+          not even evaluate function outside of box constrained area)
+        * once linear constraints are enforced, algorithm will not violate them
+        * no such guarantees can be provided for nonlinear constraints,  but  once
+          nonlinear constraints are enforced, algorithm will try  to  respect them
+          as much as possible
+        * numerical differentiation does not  violate  box  constraints  (although
+          general linear and nonlinear ones can be violated during differentiation)
+
+        However, following drawbacks can be noted:
+        * algorithm performance decreased on problems with dense constraints
+        * it has higher iteration cost than AUL - we have to solve an  LP  problem
+          at each step.
+
+        We recommend this algorithm as a first step; as soon as you make sure that
+        it converges, you can try switching to AUL which is sometimes much faster.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 02.04.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetalgoslp(minnlcstate state,
+            alglib.xparams _params)
+        {
+            state.solvertype = 1;
+        }
+
+
+        /*************************************************************************
+        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 MinNLCOptimize().
+
+        NOTE: algorithm passes two parameters to rep() callback  -  current  point
+              and penalized function value at current point. Important -  function
+              value which is returned is NOT function being minimized. It  is  sum
+              of the value of the function being minimized - and penalty term.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcsetxrep(minnlcstate state,
+            bool needxrep,
+            alglib.xparams _params)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+
+        NOTES:
+
+        1. This function has two different implementations: one which  uses  exact
+           (analytical) user-supplied Jacobian, and one which uses  only  function
+           vector and numerically  differentiates  function  in  order  to  obtain
+           gradient.
+
+           Depending  on  the  specific  function  used to create optimizer object
+           you should choose appropriate variant of MinNLCOptimize() -  one  which
+           accepts function AND Jacobian or one which accepts ONLY function.
+
+           Be careful to choose variant of MinNLCOptimize()  which  corresponds to
+           your optimization scheme! Table below lists different  combinations  of
+           callback (function/gradient) passed to MinNLCOptimize()   and  specific
+           function used to create optimizer.
+
+
+                             |         USER PASSED TO MinNLCOptimize()
+           CREATED WITH      |  function only   |  function and gradient
+           ------------------------------------------------------------
+           MinNLCCreateF()   |     works               FAILS
+           MinNLCCreate()    |     FAILS               works
+
+           Here "FAILS" denotes inappropriate combinations  of  optimizer creation
+           function  and  MinNLCOptimize()  version.   Attemps   to    use    such
+           combination will lead to exception. Either  you  did  not pass gradient
+           when it WAS needed or you passed gradient when it was NOT needed.
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool minnlciteration(minnlcstate state,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+            int k = 0;
+            int n = 0;
+            int ng = 0;
+            int nh = 0;
+            double vleft = 0;
+            double vright = 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];
+                k = state.rstate.ia[1];
+                n = state.rstate.ia[2];
+                ng = state.rstate.ia[3];
+                nh = state.rstate.ia[4];
+                vleft = state.rstate.ra[0];
+                vright = state.rstate.ra[1];
+            }
+            else
+            {
+                i = 359;
+                k = -58;
+                n = -919;
+                ng = -909;
+                nh = 81;
+                vleft = 255;
+                vright = 74;
+            }
+            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
+            //
+            
+            //
+            // Init
+            //
+            state.userterminationneeded = false;
+            state.repterminationtype = 0;
+            state.repinneriterationscount = 0;
+            state.repouteriterationscount = 0;
+            state.repnfev = 0;
+            state.repdbgphase0its = 0;
+            state.repbcerr = 0;
+            state.repbcidx = -1;
+            state.replcerr = 0;
+            state.replcidx = -1;
+            state.repnlcerr = 0;
+            state.repnlcidx = -1;
+            n = state.n;
+            ng = state.ng;
+            nh = state.nh;
+            clearrequestfields(state, _params);
+            alglib.ap.assert(state.smoothnessguardlevel==0 || state.smoothnessguardlevel==1, "MinNLCIteration: integrity check failed");
+            optserv.smoothnessmonitorinit(state.smonitor, n, 1+ng+nh, state.smoothnessguardlevel>0, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.lastscaleused[i] = state.s[i];
+            }
+            
+            //
+            // Check correctness of box constraints
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.hasbndl[i] && state.hasbndu[i] )
+                {
+                    if( (double)(state.bndl[i])>(double)(state.bndu[i]) )
+                    {
+                        state.repterminationtype = -3;
+                        state.repbcerr = state.bndl[i]-state.bndu[i];
+                        state.repbcidx = i;
+                        result = false;
+                        return result;
+                    }
+                }
+            }
+            
+            //
+            // Test gradient
+            //
+            if( !((double)(state.diffstep)==(double)(0) && (double)(state.teststep)>(double)(0)) )
+            {
+                goto lbl_16;
+            }
+        lbl_18:
+            if( !optserv.smoothnessmonitorcheckgradientatx0(state.smonitor, state.xstart, state.s, state.bndl, state.bndu, true, state.teststep, _params) )
+            {
+                goto lbl_19;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.smonitor.x[i];
+            }
+            state.needfij = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfij = false;
+            for(i=0; i<=ng+nh; i++)
+            {
+                state.smonitor.fi[i] = state.fi[i];
+                for(k=0; k<=n-1; k++)
+                {
+                    state.smonitor.j[i,k] = state.j[i,k];
+                }
+            }
+            goto lbl_18;
+        lbl_19:
+        lbl_16:
+            
+            //
+            // AUL solver
+            //
+            if( state.solvertype!=0 )
+            {
+                goto lbl_20;
+            }
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                apserv.rvectorsetlengthatleast(ref state.xbase, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.fbase, 1+ng+nh, _params);
+                apserv.rvectorsetlengthatleast(ref state.fm2, 1+ng+nh, _params);
+                apserv.rvectorsetlengthatleast(ref state.fm1, 1+ng+nh, _params);
+                apserv.rvectorsetlengthatleast(ref state.fp1, 1+ng+nh, _params);
+                apserv.rvectorsetlengthatleast(ref state.fp2, 1+ng+nh, _params);
+            }
+            state.rstateaul.ia = new int[8+1];
+            state.rstateaul.ra = new double[7+1];
+            state.rstateaul.stage = -1;
+        lbl_22:
+            if( !auliteration(state, state.smonitor, _params) )
+            {
+                goto lbl_23;
+            }
+            
+            //
+            // Numerical differentiation (if needed) - intercept NeedFiJ
+            // request and replace it by sequence of NeedFi requests
+            //
+            if( !((double)(state.diffstep)!=(double)(0) && state.needfij) )
+            {
+                goto lbl_24;
+            }
+            state.needfij = false;
+            state.needfi = true;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xbase[i_] = state.x[i_];
+            }
+            k = 0;
+        lbl_26:
+            if( k>n-1 )
+            {
+                goto lbl_28;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.x[k] = state.x[k]-state.s[k]*state.diffstep;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.fm2[i_] = state.fi[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.x[k] = state.x[k]-0.5*state.s[k]*state.diffstep;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            for(i_=0; i_<=ng+nh;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]+0.5*state.s[k]*state.diffstep;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.fp1[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;
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.fp2[i_] = state.fi[i_];
+            }
+            for(i=0; i<=ng+nh; i++)
+            {
+                state.j[i,k] = (8*(state.fp1[i]-state.fm1[i])-(state.fp2[i]-state.fm2[i]))/(6*state.diffstep*state.s[i]);
+            }
+            k = k+1;
+            goto lbl_26;
+        lbl_28:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            
+            //
+            // Restore previous values of fields and continue
+            //
+            state.needfi = false;
+            state.needfij = true;
+            goto lbl_22;
+        lbl_24:
+            
+            //
+            // Forward request to caller
+            //
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            goto lbl_22;
+        lbl_23:
+            result = false;
+            return result;
+        lbl_20:
+            
+            //
+            // SLP solver
+            //
+            if( state.solvertype!=1 )
+            {
+                goto lbl_29;
+            }
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                apserv.rvectorsetlengthatleast(ref state.xbase, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.fbase, 1+ng+nh, _params);
+                apserv.rvectorsetlengthatleast(ref state.fm2, 1+ng+nh, _params);
+                apserv.rvectorsetlengthatleast(ref state.fm1, 1+ng+nh, _params);
+                apserv.rvectorsetlengthatleast(ref state.fp1, 1+ng+nh, _params);
+                apserv.rvectorsetlengthatleast(ref state.fp2, 1+ng+nh, _params);
+            }
+            nlcslp.minslpinitbuf(state.bndl, state.bndu, state.s, state.xstart, n, state.cleic, state.lcsrcidx, state.nec, state.nic, state.ng, state.nh, state.epsx, state.maxits, state.slpsolverstate, _params);
+        lbl_31:
+            if( !nlcslp.minslpiteration(state.slpsolverstate, state.smonitor, state.userterminationneeded, _params) )
+            {
+                goto lbl_32;
+            }
+            
+            //
+            // Forward request to caller
+            //
+            if( !state.slpsolverstate.needfij )
+            {
+                goto lbl_33;
+            }
+            
+            //
+            // Evaluate target function/Jacobian
+            //
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_35;
+            }
+            
+            //
+            // Analytic Jacobian is provided
+            //
+            unscale(state, state.slpsolverstate.x, state.slpsolverstate.scaledbndl, state.slpsolverstate.scaledbndu, state.x, _params);
+            state.needfij = true;
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.needfij = false;
+            for(i=0; i<=ng+nh; i++)
+            {
+                state.slpsolverstate.fi[i] = state.fi[i];
+                for(k=0; k<=n-1; k++)
+                {
+                    state.slpsolverstate.j[i,k] = state.j[i,k]*state.s[k];
+                }
+            }
+            goto lbl_36;
+        lbl_35:
+            
+            //
+            // Numerical differentiation
+            //
+            state.needfij = false;
+            state.needfi = true;
+            unscale(state, state.slpsolverstate.x, state.slpsolverstate.scaledbndl, state.slpsolverstate.scaledbndu, state.xbase, _params);
+            k = 0;
+        lbl_37:
+            if( k>n-1 )
+            {
+                goto lbl_39;
+            }
+            vleft = state.xbase[k]-state.s[k]*state.diffstep;
+            vright = state.xbase[k]+state.s[k]*state.diffstep;
+            if( !((state.hasbndl[k] && (double)(vleft)<(double)(state.bndl[k])) || (state.hasbndu[k] && (double)(vright)>(double)(state.bndu[k]))) )
+            {
+                goto lbl_40;
+            }
+            
+            //
+            // Box constraint is violated by 4-point centered formula, use 2-point uncentered one
+            //
+            if( state.hasbndl[k] && (double)(vleft)<(double)(state.bndl[k]) )
+            {
+                vleft = state.bndl[k];
+            }
+            if( state.hasbndu[k] && (double)(vright)>(double)(state.bndu[k]) )
+            {
+                vright = state.bndu[k];
+            }
+            alglib.ap.assert((double)(vleft)<=(double)(vright), "MinNLC: integrity check failed");
+            if( (double)(vleft)==(double)(vright) )
+            {
+                
+                //
+                // Fixed variable
+                //
+                for(i=0; i<=ng+nh; i++)
+                {
+                    state.j[i,k] = 0;
+                }
+                goto lbl_38;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.x[k] = vleft;
+            state.rstate.stage = 8;
+            goto lbl_rcomm;
+        lbl_8:
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.fm1[i_] = state.fi[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.x[k] = vright;
+            state.rstate.stage = 9;
+            goto lbl_rcomm;
+        lbl_9:
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.fp1[i_] = state.fi[i_];
+            }
+            for(i=0; i<=ng+nh; i++)
+            {
+                state.j[i,k] = (state.fp1[i]-state.fm1[i])/(vright-vleft);
+            }
+            goto lbl_41;
+        lbl_40:
+            
+            //
+            // 4-point centered formula does not violate box constraints
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.x[k] = state.x[k]-state.s[k]*state.diffstep;
+            state.rstate.stage = 10;
+            goto lbl_rcomm;
+        lbl_10:
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.fm2[i_] = state.fi[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.x[k] = state.x[k]-0.5*state.s[k]*state.diffstep;
+            state.rstate.stage = 11;
+            goto lbl_rcomm;
+        lbl_11:
+            for(i_=0; i_<=ng+nh;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]+0.5*state.s[k]*state.diffstep;
+            state.rstate.stage = 12;
+            goto lbl_rcomm;
+        lbl_12:
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.fp1[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;
+            state.rstate.stage = 13;
+            goto lbl_rcomm;
+        lbl_13:
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.fp2[i_] = state.fi[i_];
+            }
+            for(i=0; i<=ng+nh; i++)
+            {
+                state.j[i,k] = (8*(state.fp1[i]-state.fm1[i])-(state.fp2[i]-state.fm2[i]))/(6*state.diffstep*state.s[k]);
+            }
+        lbl_41:
+        lbl_38:
+            k = k+1;
+            goto lbl_37;
+        lbl_39:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.rstate.stage = 14;
+            goto lbl_rcomm;
+        lbl_14:
+            state.needfi = false;
+            state.needfij = true;
+            for(i=0; i<=ng+nh; i++)
+            {
+                state.slpsolverstate.fi[i] = state.fi[i];
+                for(k=0; k<=n-1; k++)
+                {
+                    state.slpsolverstate.j[i,k] = state.j[i,k]*state.s[k];
+                }
+            }
+        lbl_36:
+            goto lbl_31;
+        lbl_33:
+            if( !state.slpsolverstate.xupdated )
+            {
+                goto lbl_42;
+            }
+            
+            //
+            // Report current point
+            //
+            if( !state.xrep )
+            {
+                goto lbl_44;
+            }
+            unscale(state, state.slpsolverstate.x, state.slpsolverstate.scaledbndl, state.slpsolverstate.scaledbndu, state.x, _params);
+            state.f = state.slpsolverstate.f;
+            state.xupdated = true;
+            state.rstate.stage = 15;
+            goto lbl_rcomm;
+        lbl_15:
+            state.xupdated = false;
+        lbl_44:
+            goto lbl_31;
+        lbl_42:
+            alglib.ap.assert(state.slpsolverstate.needfij, "NLC:SLP:request");
+            goto lbl_31;
+        lbl_32:
+            state.repterminationtype = state.slpsolverstate.repterminationtype;
+            state.repouteriterationscount = state.slpsolverstate.repouteriterationscount;
+            state.repinneriterationscount = state.slpsolverstate.repinneriterationscount;
+            state.repbcerr = state.slpsolverstate.repbcerr;
+            state.repbcidx = state.slpsolverstate.repbcidx;
+            state.replcerr = state.slpsolverstate.replcerr;
+            state.replcidx = state.slpsolverstate.replcidx;
+            state.repnlcerr = state.slpsolverstate.repnlcerr;
+            state.repnlcidx = state.slpsolverstate.repnlcidx;
+            unscale(state, state.slpsolverstate.stepkx, state.slpsolverstate.scaledbndl, state.slpsolverstate.scaledbndu, state.xc, _params);
+            result = false;
+            return result;
+        lbl_29:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = i;
+            state.rstate.ia[1] = k;
+            state.rstate.ia[2] = n;
+            state.rstate.ia[3] = ng;
+            state.rstate.ia[4] = nh;
+            state.rstate.ra[0] = vleft;
+            state.rstate.ra[1] = vright;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This  function  activates/deactivates verification  of  the  user-supplied
+        analytic gradient/Jacobian.
+
+        Upon  activation  of  this  option  OptGuard  integrity  checker  performs
+        numerical differentiation of your target  function  (constraints)  at  the
+        initial point (note: future versions may also perform check  at  the final
+        point) and compares numerical gradient/Jacobian with analytic one provided
+        by you.
+
+        If difference is too large, an error flag is set and optimization  session
+        continues. After optimization session is over, you can retrieve the report
+        which stores both gradients/Jacobians, and specific components highlighted
+        as suspicious by the OptGuard.
+
+        The primary OptGuard report can be retrieved with minnlcoptguardresults().
+
+        IMPORTANT: gradient check is a high-overhead option which  will  cost  you
+                   about 3*N additional function evaluations. In many cases it may
+                   cost as much as the rest of the optimization session.
+                   
+                   YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO
+                   CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY.
+
+        NOTE: unlike previous incarnation of the gradient checking code,  OptGuard
+              does NOT interrupt optimization even if it discovers bad gradient.
+              
+        INPUT PARAMETERS:
+            State       -   structure used to store algorithm state
+            TestStep    -   verification step used for numerical differentiation:
+                            * TestStep=0 turns verification off
+                            * TestStep>0 activates verification
+                            You should carefully choose TestStep. Value  which  is
+                            too large (so large that  function  behavior  is  non-
+                            cubic at this scale) will lead  to  false  alarms. Too
+                            short step will result in rounding  errors  dominating
+                            numerical derivative.
+                            
+                            You may use different step for different parameters by
+                            means of setting scale with minnlcsetscale().
+
+        === EXPLANATION ==========================================================                    
+
+        In order to verify gradient algorithm performs following steps:
+          * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i],
+            where X[i] is i-th component of the initial point and S[i] is a  scale
+            of i-th parameter
+          * F(X) is evaluated at these trial points
+          * we perform one more evaluation in the middle point of the interval
+          * we  build  cubic  model using function values and derivatives at trial
+            points and we compare its prediction with actual value in  the  middle
+            point
+
+          -- ALGLIB --
+             Copyright 15.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcoptguardgradient(minnlcstate state,
+            double teststep,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(teststep), "MinNLCOptGuardGradient: TestStep contains NaN or INF");
+            alglib.ap.assert((double)(teststep)>=(double)(0), "MinNLCOptGuardGradient: invalid argument TestStep(TestStep<0)");
+            state.teststep = teststep;
+        }
+
+
+        /*************************************************************************
+        This  function  activates/deactivates nonsmoothness monitoring  option  of
+        the  OptGuard  integrity  checker. Smoothness  monitor  silently  observes
+        solution process and tries to detect ill-posed problems, i.e. ones with:
+        a) discontinuous target function (non-C0) and/or constraints
+        b) nonsmooth     target function (non-C1) and/or constraints
+
+        Smoothness monitoring does NOT interrupt optimization  even if it suspects
+        that your problem is nonsmooth. It just sets corresponding  flags  in  the
+        OptGuard report which can be retrieved after optimization is over.
+
+        Smoothness monitoring is a moderate overhead option which often adds  less
+        than 1% to the optimizer running time. Thus, you can use it even for large
+        scale problems.
+
+        NOTE: OptGuard does  NOT  guarantee  that  it  will  always  detect  C0/C1
+              continuity violations.
+              
+              First, minor errors are hard to  catch - say, a 0.0001 difference in
+              the model values at two sides of the gap may be due to discontinuity
+              of the model - or simply because the model has changed.
+              
+              Second, C1-violations  are  especially  difficult  to  detect  in  a
+              noninvasive way. The optimizer usually  performs  very  short  steps
+              near the nonsmoothness, and differentiation  usually   introduces  a
+              lot of numerical noise.  It  is  hard  to  tell  whether  some  tiny
+              discontinuity in the slope is due to real nonsmoothness or just  due
+              to numerical noise alone.
+              
+              Our top priority was to avoid false positives, so in some rare cases
+              minor errors may went unnoticed (however, in most cases they can  be
+              spotted with restart from different initial point).
+
+        INPUT PARAMETERS:
+            state   -   algorithm state
+            level   -   monitoring level:
+                        * 0 - monitoring is disabled
+                        * 1 - noninvasive low-overhead monitoring; function values
+                              and/or gradients are recorded, but OptGuard does not
+                              try to perform additional evaluations  in  order  to
+                              get more information about suspicious locations.
+          
+        === EXPLANATION ==========================================================
+
+        One major source of headache during optimization  is  the  possibility  of
+        the coding errors in the target function/constraints (or their gradients).
+        Such  errors   most   often   manifest   themselves  as  discontinuity  or
+        nonsmoothness of the target/constraints.
+
+        Another frequent situation is when you try to optimize something involving
+        lots of min() and max() operations, i.e. nonsmooth target. Although not  a
+        coding error, it is nonsmoothness anyway - and smooth  optimizers  usually
+        stop right after encountering nonsmoothness, well before reaching solution.
+
+        OptGuard integrity checker helps you to catch such situations: it monitors
+        function values/gradients being passed  to  the  optimizer  and  tries  to
+        errors. Upon discovering suspicious pair of points it  raises  appropriate
+        flag (and allows you to continue optimization). When optimization is done,
+        you can study OptGuard result.
+
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcoptguardsmoothness(minnlcstate state,
+            int level,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(level==0 || level==1, "MinNLCOptGuardSmoothness: unexpected value of level parameter");
+            state.smoothnessguardlevel = level;
+        }
+
+
+        /*************************************************************************
+        Results of OptGuard integrity check, should be called  after  optimization
+        session is over.
+
+        === PRIMARY REPORT =======================================================
+
+        OptGuard performs several checks which are intended to catch common errors
+        in the implementation of nonlinear function/gradient:
+        * incorrect analytic gradient
+        * discontinuous (non-C0) target functions (constraints)
+        * nonsmooth     (non-C1) target functions (constraints)
+
+        Each of these checks is activated with appropriate function:
+        * minnlcoptguardgradient() for gradient verification
+        * minnlcoptguardsmoothness() for C0/C1 checks
+
+        Following flags are set when these errors are suspected:
+        * rep.badgradsuspected, and additionally:
+          * rep.badgradfidx for specific function (Jacobian row) suspected
+          * rep.badgradvidx for specific variable (Jacobian column) suspected
+          * rep.badgradxbase, a point where gradient/Jacobian is tested
+          * rep.badgraduser, user-provided gradient/Jacobian
+          * rep.badgradnum, reference gradient/Jacobian obtained via numerical
+            differentiation
+        * rep.nonc0suspected, and additionally:
+          * rep.nonc0fidx - an index of specific function violating C0 continuity
+        * rep.nonc1suspected, and additionally
+          * rep.nonc1fidx - an index of specific function violating C1 continuity
+        Here function index 0 means  target function, index 1  or  higher  denotes
+        nonlinear constraints.
+
+        === ADDITIONAL REPORTS/LOGS ==============================================
+            
+        Several different tests are performed to catch C0/C1 errors, you can  find
+        out specific test signaled error by looking to:
+        * rep.nonc0test0positive, for non-C0 test #0
+        * rep.nonc1test0positive, for non-C1 test #0
+        * rep.nonc1test1positive, for non-C1 test #1
+
+        Additional information (including line search logs)  can  be  obtained  by
+        means of:
+        * minnlcoptguardnonc1test0results()
+        * minnlcoptguardnonc1test1results()
+        which return detailed error reports, specific points where discontinuities
+        were found, and so on.
+
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            rep     -   generic OptGuard report;  more  detailed  reports  can  be
+                        retrieved with other functions.
+
+        NOTE: false negatives (nonsmooth problems are not identified as  nonsmooth
+              ones) are possible although unlikely.
+              
+              The reason  is  that  you  need  to  make several evaluations around
+              nonsmoothness  in  order  to  accumulate  enough  information  about
+              function curvature. Say, if you start right from the nonsmooth point,
+              optimizer simply won't get enough data to understand what  is  going
+              wrong before it terminates due to abrupt changes in the  derivative.
+              It is also  possible  that  "unlucky"  step  will  move  us  to  the
+              termination too quickly.
+              
+              Our current approach is to have less than 0.1%  false  negatives  in
+              our test examples  (measured  with  multiple  restarts  from  random
+              points), and to have exactly 0% false positives.
+           
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcoptguardresults(minnlcstate state,
+            optguardapi.optguardreport rep,
+            alglib.xparams _params)
+        {
+            optserv.smoothnessmonitorexportreport(state.smonitor, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Detailed results of the OptGuard integrity check for nonsmoothness test #0
+
+        Nonsmoothness (non-C1) test #0 studies  function  values  (not  gradient!)
+        obtained during line searches and monitors  behavior  of  the  directional
+        derivative estimate.
+
+        This test is less powerful than test #1, but it does  not  depend  on  the
+        gradient values and thus it is more robust against artifacts introduced by
+        numerical differentiation.
+
+        Two reports are returned:
+        * a "strongest" one, corresponding  to  line   search  which  had  highest
+          value of the nonsmoothness indicator
+        * a "longest" one, corresponding to line search which  had  more  function
+          evaluations, and thus is more detailed
+
+        In both cases following fields are returned:
+
+        * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+          did not notice anything (in the latter cases fields below are empty).
+        * fidx - is an index of the function (0 for  target  function, 1 or higher
+          for nonlinear constraints) which is suspected of being "non-C1"
+        * x0[], d[] - arrays of length N which store initial point  and  direction
+          for line search (d[] can be normalized, but does not have to)
+        * stp[], f[] - arrays of length CNT which store step lengths and  function
+          values at these points; f[i] is evaluated in x0+stp[i]*d.
+        * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+          between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+          with  most  likely  position  of  the  violation  between  stpidxa+1 and
+          stpidxa+2.
+          
+        ==========================================================================
+        = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+        =                   see where C1 continuity is violated.
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            strrep  -   C1 test #0 "strong" report
+            lngrep  -   C1 test #0 "long" report
+            
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcoptguardnonc1test0results(minnlcstate state,
+            optguardapi.optguardnonc1test0report strrep,
+            optguardapi.optguardnonc1test0report lngrep,
+            alglib.xparams _params)
+        {
+            optguardapi.smoothnessmonitorexportc1test0report(state.smonitor.nonc1test0strrep, state.lastscaleused, strrep, _params);
+            optguardapi.smoothnessmonitorexportc1test0report(state.smonitor.nonc1test0lngrep, state.lastscaleused, lngrep, _params);
+        }
+
+
+        /*************************************************************************
+        Detailed results of the OptGuard integrity check for nonsmoothness test #1
+
+        Nonsmoothness (non-C1)  test  #1  studies  individual  components  of  the
+        gradient computed during line search.
+
+        When precise analytic gradient is provided this test is more powerful than
+        test #0  which  works  with  function  values  and  ignores  user-provided
+        gradient.  However,  test  #0  becomes  more   powerful   when   numerical
+        differentiation is employed (in such cases test #1 detects  higher  levels
+        of numerical noise and becomes too conservative).
+
+        This test also tells specific components of the gradient which violate  C1
+        continuity, which makes it more informative than #0, which just tells that
+        continuity is violated.
+
+        Two reports are returned:
+        * a "strongest" one, corresponding  to  line   search  which  had  highest
+          value of the nonsmoothness indicator
+        * a "longest" one, corresponding to line search which  had  more  function
+          evaluations, and thus is more detailed
+
+        In both cases following fields are returned:
+
+        * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+          did not notice anything (in the latter cases fields below are empty).
+        * fidx - is an index of the function (0 for  target  function, 1 or higher
+          for nonlinear constraints) which is suspected of being "non-C1"
+        * vidx - is an index of the variable in [0,N) with nonsmooth derivative
+        * x0[], d[] - arrays of length N which store initial point  and  direction
+          for line search (d[] can be normalized, but does not have to)
+        * stp[], g[] - arrays of length CNT which store step lengths and  gradient
+          values at these points; g[i] is evaluated in  x0+stp[i]*d  and  contains
+          vidx-th component of the gradient.
+        * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+          between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+          with  most  likely  position  of  the  violation  between  stpidxa+1 and
+          stpidxa+2.
+          
+        ==========================================================================
+        = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+        =                   see where C1 continuity is violated.
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            strrep  -   C1 test #1 "strong" report
+            lngrep  -   C1 test #1 "long" report
+            
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcoptguardnonc1test1results(minnlcstate state,
+            optguardapi.optguardnonc1test1report strrep,
+            optguardapi.optguardnonc1test1report lngrep,
+            alglib.xparams _params)
+        {
+            optguardapi.smoothnessmonitorexportc1test1report(state.smonitor.nonc1test1strrep, state.lastscaleused, strrep, _params);
+            optguardapi.smoothnessmonitorexportc1test1report(state.smonitor.nonc1test1lngrep, state.lastscaleused, lngrep, _params);
+        }
+
+
+        /*************************************************************************
+        MinNLC results:  the  solution  found,  completion  codes  and  additional
+        information.
+
+        If you activated OptGuard integrity checking functionality and want to get
+        OptGuard report, it can be retrieved with:
+        * minnlcoptguardresults() - for a primary report about (a) suspected C0/C1
+          continuity violations and (b) errors in the analytic gradient.
+        * minnlcoptguardnonc1test0results() - for C1 continuity violation test #0,
+          detailed line search log
+        * minnlcoptguardnonc1test1results() - for C1 continuity violation test #1,
+          detailed line search log
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[0..N-1], solution
+            Rep     -   optimization report, contains information about completion
+                        code, constraint violation at the solution and so on.
+                        
+                        You   should   check   rep.terminationtype  in  order   to
+                        distinguish successful termination from unsuccessful one:
+                        
+                        === FAILURE CODES ===
+                        * -8    internal  integrity control  detected  infinite or
+                                NAN   values    in   function/gradient.   Abnormal
+                                termination signalled.
+                        * -3    box  constraints are infeasible.
+                                Note: infeasibility of  non-box  constraints  does
+                                      NOT trigger emergency completion;  you  have
+                                      to examine rep.bcerr/rep.lcerr/rep.nlcerr to
+                                      detect possibly inconsistent constraints.
+                                      
+                        === SUCCESS CODES ===
+                        *  2   scaled step is no more than EpsX.
+                        *  5   MaxIts steps were taken.
+                        *  8   user   requested    algorithm    termination    via
+                               minnlcrequesttermination(), last accepted point  is
+                               returned.
+                        
+                        More information about fields of this  structure  can  be
+                        found in the comments on minnlcreport datatype.
+           
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcresults(minnlcstate state,
+            ref double[] x,
+            minnlcreport rep,
+            alglib.xparams _params)
+        {
+            x = new double[0];
+
+            minnlcresultsbuf(state, ref x, rep, _params);
+        }
+
+
+        /*************************************************************************
+        NLC results
+
+        Buffered implementation of MinNLCResults() 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 minnlcresultsbuf(minnlcstate state,
+            ref double[] x,
+            minnlcreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            if( alglib.ap.len(x)<state.n )
+            {
+                x = new double[state.n];
+            }
+            rep.iterationscount = state.repinneriterationscount;
+            rep.nfev = state.repnfev;
+            rep.terminationtype = state.repterminationtype;
+            rep.bcerr = state.repbcerr;
+            rep.bcidx = state.repbcidx;
+            rep.lcerr = state.replcerr;
+            rep.lcidx = state.replcidx;
+            rep.nlcerr = state.repnlcerr;
+            rep.nlcidx = state.repnlcidx;
+            rep.dbgphase0its = state.repdbgphase0its;
+            if( state.repterminationtype>0 )
+            {
+                for(i_=0; i_<=state.n-1;i_++)
+                {
+                    x[i_] = state.xc[i_];
+                }
+            }
+            else
+            {
+                for(i=0; i<=state.n-1; i++)
+                {
+                    x[i] = Double.NaN;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine submits request for termination of running  optimizer.  It
+        should be called from user-supplied callback when user decides that it  is
+        time to "smoothly" terminate optimization process.  As  result,  optimizer
+        stops at point which was "current accepted" when termination  request  was
+        submitted and returns error code 8 (successful termination).
+
+        INPUT PARAMETERS:
+            State   -   optimizer structure
+
+        NOTE: after  request  for  termination  optimizer  may   perform   several
+              additional calls to user-supplied callbacks. It does  NOT  guarantee
+              to stop immediately - it just guarantees that these additional calls
+              will be discarded later.
+
+        NOTE: calling this function on optimizer which is NOT running will have no
+              effect.
+              
+        NOTE: multiple calls to this function are possible. First call is counted,
+              subsequent calls are silently ignored.
+
+          -- ALGLIB --
+             Copyright 08.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcrequesttermination(minnlcstate state,
+            alglib.xparams _params)
+        {
+            state.userterminationneeded = true;
+        }
+
+
+        /*************************************************************************
+        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 MinNLCCreate call.
+            X       -   new starting point.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcrestartfrom(minnlcstate state,
+            double[] x,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i_ = 0;
+
+            n = state.n;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinNLCRestartFrom: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "MinNLCRestartFrom: 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.ra = new double[1+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state, _params);
+        }
+
+
+        /*************************************************************************
+        Penalty function for equality constraints.
+        INPUT PARAMETERS:
+            Alpha   -   function argument. Penalty function becomes large when
+                        Alpha approaches -1 or +1. It is defined for Alpha<=-1 or
+                        Alpha>=+1 - in this case infinite value is returned.
+                        
+        OUTPUT PARAMETERS:
+            F       -   depending on Alpha:
+                        * for Alpha in (-1+eps,+1-eps), F=F(Alpha)
+                        * for Alpha outside of interval, F is some very large number
+            DF      -   depending on Alpha:
+                        * for Alpha in (-1+eps,+1-eps), DF=dF(Alpha)/dAlpha, exact
+                          numerical derivative.
+                        * otherwise, it is zero
+            D2F     -   second derivative
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcequalitypenaltyfunction(double alpha,
+            ref double f,
+            ref double df,
+            ref double d2f,
+            alglib.xparams _params)
+        {
+            f = 0;
+            df = 0;
+            d2f = 0;
+
+            f = 0.5*alpha*alpha;
+            df = alpha;
+            d2f = 1.0;
+        }
+
+
+        /*************************************************************************
+        "Penalty" function  for  inequality  constraints,  which  is multiplied by
+        penalty coefficient Rho.
+
+        "Penalty" function plays only supplementary role - it helps  to  stabilize
+        algorithm when solving non-convex problems. Because it  is  multiplied  by
+        fixed and large  Rho  -  not  Lagrange  multiplier  Nu  which  may  become
+        arbitrarily small! - it enforces  convexity  of  the  problem  behind  the
+        boundary of the feasible area.
+
+        This function is zero at the feasible area and in the close  neighborhood,
+        it becomes non-zero only at some distance (scaling is essential!) and grows
+        quadratically.
+
+        Penalty function must enter augmented Lagrangian as
+            Rho*PENALTY(x-lowerbound)
+        with corresponding changes being made for upper bound or  other  kinds  of
+        constraints.
+
+        INPUT PARAMETERS:
+            Alpha   -   function argument. Typically, if we have active constraint
+                        with precise Lagrange multiplier, we have Alpha  around 1.
+                        Large positive Alpha's correspond to  inner  area  of  the
+                        feasible set. Alpha<1 corresponds to  outer  area  of  the
+                        feasible set.
+            StabilizingPoint- point where F becomes  non-zero.  Must  be  negative
+                        value, at least -1, large values (hundreds) are possible.
+                        
+        OUTPUT PARAMETERS:
+            F       -   F(Alpha)
+            DF      -   DF=dF(Alpha)/dAlpha, exact derivative
+            D2F     -   second derivative
+            
+        NOTE: it is important to  have  significantly  non-zero  StabilizingPoint,
+              because when it  is  large,  shift  term  does  not  interfere  with
+              Lagrange  multipliers  converging  to  their  final  values.   Thus,
+              convergence of such modified AUL algorithm is  still  guaranteed  by
+              same set of theorems.
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcinequalitypenaltyfunction(double alpha,
+            double stabilizingpoint,
+            ref double f,
+            ref double df,
+            ref double d2f,
+            alglib.xparams _params)
+        {
+            f = 0;
+            df = 0;
+            d2f = 0;
+
+            if( (double)(alpha)>=(double)(stabilizingpoint) )
+            {
+                f = 0.0;
+                df = 0.0;
+                d2f = 0.0;
+            }
+            else
+            {
+                alpha = alpha-stabilizingpoint;
+                f = 0.5*alpha*alpha;
+                df = alpha;
+                d2f = 1.0;
+            }
+        }
+
+
+        /*************************************************************************
+        "Shift" function  for  inequality  constraints,  which  is  multiplied  by
+        corresponding Lagrange multiplier.
+
+        "Shift" function is a main factor which enforces  inequality  constraints.
+        Inequality penalty function plays only supplementary role  -  it  prevents
+        accidental step deep into infeasible area  when  working  with  non-convex
+        problems (read comments on corresponding function for more information).
+
+        Shift function must enter augmented Lagrangian as
+            Nu/Rho*SHIFT((x-lowerbound)*Rho+1)
+        with corresponding changes being made for upper bound or  other  kinds  of
+        constraints.
+
+        INPUT PARAMETERS:
+            Alpha   -   function argument. Typically, if we have active constraint
+                        with precise Lagrange multiplier, we have Alpha  around 1.
+                        Large positive Alpha's correspond to  inner  area  of  the
+                        feasible set. Alpha<1 corresponds to  outer  area  of  the
+                        feasible set.
+                        
+        OUTPUT PARAMETERS:
+            F       -   F(Alpha)
+            DF      -   DF=dF(Alpha)/dAlpha, exact derivative
+            D2F     -   second derivative
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnlcinequalityshiftfunction(double alpha,
+            ref double f,
+            ref double df,
+            ref double d2f,
+            alglib.xparams _params)
+        {
+            f = 0;
+            df = 0;
+            d2f = 0;
+
+            if( (double)(alpha)>=(double)(0.5) )
+            {
+                f = -Math.Log(alpha);
+                df = -(1/alpha);
+                d2f = 1/(alpha*alpha);
+            }
+            else
+            {
+                f = 2*alpha*alpha-4*alpha+(Math.Log(2)+1.5);
+                df = 4*alpha-4;
+                d2f = 4;
+            }
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forget to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(minnlcstate state,
+            alglib.xparams _params)
+        {
+            state.needfi = false;
+            state.needfij = false;
+            state.xupdated = false;
+        }
+
+
+        /*************************************************************************
+        Internal initialization subroutine.
+        Sets default NLC solver with default criteria.
+        *************************************************************************/
+        private static void minnlcinitinternal(int n,
+            double[] x,
+            double diffstep,
+            minnlcstate state,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double[,] c = new double[0,0];
+            int[] ct = new int[0];
+
+            
+            //
+            // Default params
+            //
+            state.stabilizingpoint = -2.0;
+            state.initialinequalitymultiplier = 1.0;
+            
+            //
+            // Smoothness monitor, default init
+            //
+            state.teststep = 0;
+            state.smoothnessguardlevel = 0;
+            optserv.smoothnessmonitorinit(state.smonitor, 0, 0, false, _params);
+            
+            //
+            // Initialize other params
+            //
+            state.n = n;
+            state.diffstep = diffstep;
+            state.userterminationneeded = false;
+            state.bndl = new double[n];
+            state.hasbndl = new bool[n];
+            state.bndu = new double[n];
+            state.hasbndu = new bool[n];
+            state.s = new double[n];
+            state.lastscaleused = new double[n];
+            state.xstart = new double[n];
+            state.xc = new double[n];
+            state.x = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                state.bndl[i] = Double.NegativeInfinity;
+                state.hasbndl[i] = false;
+                state.bndu[i] = Double.PositiveInfinity;
+                state.hasbndu[i] = false;
+                state.s[i] = 1.0;
+                state.lastscaleused[i] = 1.0;
+                state.xstart[i] = x[i];
+                state.xc[i] = x[i];
+            }
+            minnlcsetlc(state, c, ct, 0, _params);
+            minnlcsetnlc(state, 0, 0, _params);
+            minnlcsetcond(state, 0.0, 0, _params);
+            minnlcsetxrep(state, false, _params);
+            minnlcsetalgoslp(state, _params);
+            minnlcsetprecexactrobust(state, 0, _params);
+            minnlcsetstpmax(state, 0.0, _params);
+            minlbfgs.minlbfgscreate(n, Math.Min(lbfgsfactor, n), x, state.auloptimizer, _params);
+            minnlcrestartfrom(state, x, _params);
+        }
+
+
+        /*************************************************************************
+        This function clears preconditioner for L-BFGS optimizer (sets it do default
+        state);
+
+        Parameters:
+            AULOptimizer    -   optimizer to tune
+            
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static void clearpreconditioner(minlbfgs.minlbfgsstate auloptimizer,
+            alglib.xparams _params)
+        {
+            minlbfgs.minlbfgssetprecdefault(auloptimizer, _params);
+        }
+
+
+        /*************************************************************************
+        This function updates preconditioner for L-BFGS optimizer.
+
+        Parameters:
+            PrecType        -   preconditioner type:
+                                * 0 for unpreconditioned iterations
+                                * 1 for inexact LBFGS
+                                * 2 for exact low rank preconditioner update after each UpdateFreq its
+                                * 3 for exact robust preconditioner update after each UpdateFreq its
+            UpdateFreq      -   update frequency
+            PrecCounter     -   iterations counter, must be zero on the first call,
+                                automatically increased  by  this  function.  This
+                                counter is used to implement "update-once-in-X-iterations"
+                                scheme.
+            AULOptimizer    -   optimizer to tune
+            X               -   current point
+            Rho             -   penalty term
+            GammaK          -   current  estimate  of  Hessian  norm   (used   for
+                                initialization of preconditioner). Can be zero, in
+                                which case Hessian is assumed to be unit.
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static void updatepreconditioner(int prectype,
+            int updatefreq,
+            ref int preccounter,
+            minlbfgs.minlbfgsstate auloptimizer,
+            double[] x,
+            double rho,
+            double gammak,
+            double[] bndl,
+            bool[] hasbndl,
+            double[] bndu,
+            bool[] hasbndu,
+            double[] nubc,
+            double[,] cleic,
+            double[] nulc,
+            double[] fi,
+            double[,] jac,
+            double[] nunlc,
+            ref double[] bufd,
+            ref double[] bufc,
+            ref double[,] bufw,
+            ref double[,] bufz,
+            ref double[] tmp0,
+            int n,
+            int nec,
+            int nic,
+            int ng,
+            int nh,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            double p = 0;
+            double dp = 0;
+            double d2p = 0;
+            bool bflag = new bool();
+            int i_ = 0;
+
+            alglib.ap.assert((double)(rho)>(double)(0), "MinNLC: integrity check failed");
+            apserv.rvectorsetlengthatleast(ref bufd, n, _params);
+            apserv.rvectorsetlengthatleast(ref bufc, nec+nic+ng+nh, _params);
+            apserv.rmatrixsetlengthatleast(ref bufw, nec+nic+ng+nh, n, _params);
+            apserv.rvectorsetlengthatleast(ref tmp0, n, _params);
+            
+            //
+            // Preconditioner before update from barrier/penalty functions
+            //
+            if( (double)(gammak)==(double)(0) )
+            {
+                gammak = 1;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                bufd[i] = gammak;
+            }
+            
+            //
+            // Update diagonal Hessian using nonlinearity from boundary constraints:
+            // * penalty term from equality constraints
+            // * shift term from inequality constraints
+            //
+            // NOTE: penalty term for inequality constraints is ignored because it
+            //       is large only in exceptional cases.
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( (hasbndl[i] && hasbndu[i]) && (double)(bndl[i])==(double)(bndu[i]) )
+                {
+                    minnlcequalitypenaltyfunction((x[i]-bndl[i])*rho, ref p, ref dp, ref d2p, _params);
+                    bufd[i] = bufd[i]+d2p*rho;
+                    continue;
+                }
+                if( hasbndl[i] )
+                {
+                    minnlcinequalityshiftfunction((x[i]-bndl[i])*rho+1, ref p, ref dp, ref d2p, _params);
+                    bufd[i] = bufd[i]+nubc[2*i+0]*d2p*rho;
+                }
+                if( hasbndu[i] )
+                {
+                    minnlcinequalityshiftfunction((bndu[i]-x[i])*rho+1, ref p, ref dp, ref d2p, _params);
+                    bufd[i] = bufd[i]+nubc[2*i+1]*d2p*rho;
+                }
+            }
+            
+            //
+            // Process linear constraints
+            //
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    bufw[i,i_] = cleic[i,i_];
+                }
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += cleic[i,i_]*x[i_];
+                }
+                v = v-cleic[i,n];
+                if( i<nec )
+                {
+                    
+                    //
+                    // Equality constraint
+                    //
+                    minnlcequalitypenaltyfunction(v*rho, ref p, ref dp, ref d2p, _params);
+                    bufc[i] = d2p*rho;
+                }
+                else
+                {
+                    
+                    //
+                    // Inequality constraint
+                    //
+                    minnlcinequalityshiftfunction(-(v*rho)+1, ref p, ref dp, ref d2p, _params);
+                    bufc[i] = nulc[i]*d2p*rho;
+                }
+            }
+            
+            //
+            // Process nonlinear constraints
+            //
+            for(i=0; i<=ng+nh-1; i++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    bufw[nec+nic+i,i_] = jac[1+i,i_];
+                }
+                v = fi[1+i];
+                if( i<ng )
+                {
+                    
+                    //
+                    // Equality constraint
+                    //
+                    minnlcequalitypenaltyfunction(v*rho, ref p, ref dp, ref d2p, _params);
+                    bufc[nec+nic+i] = d2p*rho;
+                }
+                else
+                {
+                    
+                    //
+                    // Inequality constraint
+                    //
+                    minnlcinequalityshiftfunction(-(v*rho)+1, ref p, ref dp, ref d2p, _params);
+                    bufc[nec+nic+i] = nunlc[i]*d2p*rho;
+                }
+            }
+            
+            //
+            // Add regularizer (large Rho often result in nearly-degenerate matrices;
+            // sometimes Cholesky decomposition fails without regularization).
+            //
+            // We use RegPrec*diag(W'*W) as preconditioner.
+            //
+            k = nec+nic+ng+nh;
+            for(j=0; j<=n-1; j++)
+            {
+                tmp0[j] = 0.0;
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                v = bufc[i];
+                for(j=0; j<=n-1; j++)
+                {
+                    tmp0[j] = tmp0[j]+v*bufw[i,j]*bufw[i,j];
+                }
+            }
+            for(j=0; j<=n-1; j++)
+            {
+                bufd[j] = bufd[j]+regprec*tmp0[j];
+            }
+            
+            //
+            // Apply preconditioner
+            //
+            if( prectype==1 )
+            {
+                minlbfgs.minlbfgssetprecrankklbfgsfast(auloptimizer, bufd, bufc, bufw, nec+nic+ng+nh, _params);
+            }
+            if( prectype==2 && preccounter%updatefreq==0 )
+            {
+                minlbfgs.minlbfgssetpreclowrankexact(auloptimizer, bufd, bufc, bufw, nec+nic+ng+nh, _params);
+            }
+            if( prectype==3 && preccounter%updatefreq==0 )
+            {
+                
+                //
+                // Generate full NxN dense Hessian
+                //
+                apserv.rmatrixsetlengthatleast(ref bufz, n, n, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        bufz[i,j] = 0;
+                    }
+                    bufz[i,i] = bufd[i];
+                }
+                if( nec+nic+ng+nh>0 )
+                {
+                    for(i=0; i<=nec+nic+ng+nh-1; i++)
+                    {
+                        alglib.ap.assert((double)(bufc[i])>=(double)(0), "MinNLC: updatepreconditioner() integrity failure");
+                        v = Math.Sqrt(bufc[i]);
+                        for(j=0; j<=n-1; j++)
+                        {
+                            bufw[i,j] = bufw[i,j]*v;
+                        }
+                    }
+                    ablas.rmatrixsyrk(n, nec+nic+ng+nh, 1.0, bufw, 0, 0, 2, 1.0, bufz, 0, 0, true, _params);
+                }
+                
+                //
+                // Evaluate Cholesky decomposition, set preconditioner
+                //
+                bflag = trfac.spdmatrixcholeskyrec(ref bufz, 0, n, true, ref bufd, _params);
+                alglib.ap.assert(bflag, "MinNLC: updatepreconditioner() failure, Cholesky failed");
+                minlbfgs.minlbfgssetpreccholesky(auloptimizer, bufz, true, _params);
+            }
+            apserv.inc(ref preccounter, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine adds penalty from boundary constraints to target  function
+        and its gradient. Penalty function is one which is used for main AUL cycle
+        - with Lagrange multipliers and infinite at the barrier and beyond.
+
+        Parameters:
+            X[] - current point
+            BndL[], BndU[] - boundary constraints
+            HasBndL[], HasBndU[] - I-th element is True if corresponding constraint is present
+            NuBC[] - Lagrange multipliers corresponding to constraints
+            Rho - penalty term
+            StabilizingPoint - branch point for inequality stabilizing term
+            F - function value to modify
+            G - gradient to modify
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static void penaltybc(double[] x,
+            double[] bndl,
+            bool[] hasbndl,
+            double[] bndu,
+            bool[] hasbndu,
+            double[] nubc,
+            int n,
+            double rho,
+            double stabilizingpoint,
+            ref double f,
+            double[] g,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double p = 0;
+            double dp = 0;
+            double d2p = 0;
+
+            for(i=0; i<=n-1; i++)
+            {
+                if( (hasbndl[i] && hasbndu[i]) && (double)(bndl[i])==(double)(bndu[i]) )
+                {
+                    
+                    //
+                    // I-th boundary constraint is of equality-type
+                    //
+                    minnlcequalitypenaltyfunction((x[i]-bndl[i])*rho, ref p, ref dp, ref d2p, _params);
+                    f = f+p/rho-nubc[2*i+0]*(x[i]-bndl[i]);
+                    g[i] = g[i]+dp-nubc[2*i+0];
+                    continue;
+                }
+                if( hasbndl[i] )
+                {
+                    
+                    //
+                    // Handle lower bound
+                    //
+                    minnlcinequalitypenaltyfunction(x[i]-bndl[i], stabilizingpoint, ref p, ref dp, ref d2p, _params);
+                    f = f+rho*p;
+                    g[i] = g[i]+rho*dp;
+                    minnlcinequalityshiftfunction((x[i]-bndl[i])*rho+1, ref p, ref dp, ref d2p, _params);
+                    f = f+p/rho*nubc[2*i+0];
+                    g[i] = g[i]+dp*nubc[2*i+0];
+                }
+                if( hasbndu[i] )
+                {
+                    
+                    //
+                    // Handle upper bound
+                    //
+                    minnlcinequalitypenaltyfunction(bndu[i]-x[i], stabilizingpoint, ref p, ref dp, ref d2p, _params);
+                    f = f+rho*p;
+                    g[i] = g[i]-rho*dp;
+                    minnlcinequalityshiftfunction((bndu[i]-x[i])*rho+1, ref p, ref dp, ref d2p, _params);
+                    f = f+p/rho*nubc[2*i+1];
+                    g[i] = g[i]-dp*nubc[2*i+1];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine adds penalty from  linear  constraints to target  function
+        and its gradient. Penalty function is one which is used for main AUL cycle
+        - with Lagrange multipliers and infinite at the barrier and beyond.
+
+        Parameters:
+            X[] - current point
+            CLEIC[] -   constraints matrix, first NEC rows are equality ones, next
+                        NIC rows are inequality ones. array[NEC+NIC,N+1]
+            NuLC[]  -   Lagrange multipliers corresponding to constraints,
+                        array[NEC+NIC]
+            N       -   dimensionalty
+            NEC     -   number of equality constraints
+            NIC     -   number of inequality constraints.
+            Rho - penalty term
+            StabilizingPoint - branch point for inequality stabilizing term
+            F - function value to modify
+            G - gradient to modify
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static void penaltylc(double[] x,
+            double[,] cleic,
+            double[] nulc,
+            int n,
+            int nec,
+            int nic,
+            double rho,
+            double stabilizingpoint,
+            ref double f,
+            double[] g,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double v = 0;
+            double p = 0;
+            double dp = 0;
+            double d2p = 0;
+            double fupd = 0;
+            double gupd = 0;
+            int i_ = 0;
+
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += cleic[i,i_]*x[i_];
+                }
+                v = v-cleic[i,n];
+                fupd = 0;
+                gupd = 0;
+                if( i<nec )
+                {
+                    
+                    //
+                    // Equality constraint
+                    //
+                    minnlcequalitypenaltyfunction(v*rho, ref p, ref dp, ref d2p, _params);
+                    fupd = fupd+p/rho;
+                    gupd = gupd+dp;
+                    fupd = fupd-nulc[i]*v;
+                    gupd = gupd-nulc[i];
+                }
+                else
+                {
+                    
+                    //
+                    // Inequality constraint
+                    //
+                    minnlcinequalitypenaltyfunction(-v, stabilizingpoint, ref p, ref dp, ref d2p, _params);
+                    fupd = fupd+p*rho;
+                    gupd = gupd-dp*rho;
+                    minnlcinequalityshiftfunction(-(v*rho)+1, ref p, ref dp, ref d2p, _params);
+                    fupd = fupd+p/rho*nulc[i];
+                    gupd = gupd-dp*nulc[i];
+                }
+                f = f+fupd;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    g[i_] = g[i_] + gupd*cleic[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This subroutine adds penalty from nonlinear constraints to target function
+        and its gradient. Penalty function is one which is used for main AUL cycle
+        - with Lagrange multipliers and infinite at the barrier and beyond.
+
+        Parameters:
+            Fi[] - function vector:
+                  * 1 component for function being minimized
+                  * NG components for equality constraints G_i(x)=0
+                  * NH components for inequality constraints H_i(x)<=0
+            J[]  - Jacobian matrix, array[1+NG+NH,N]
+            NuNLC[]  -   Lagrange multipliers corresponding to constraints,
+                        array[NG+NH]
+            N - number of dimensions
+            NG - number of equality constraints
+            NH - number of inequality constraints
+            Rho - penalty term
+            StabilizingPoint - branch point for inequality stabilizing term
+            F - function value to modify
+            G - gradient to modify
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static void penaltynlc(double[] fi,
+            double[,] j,
+            double[] nunlc,
+            int n,
+            int ng,
+            int nh,
+            double rho,
+            double stabilizingpoint,
+            ref double f,
+            double[] g,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double v = 0;
+            double p = 0;
+            double dp = 0;
+            double d2p = 0;
+            double fupd = 0;
+            double gupd = 0;
+            int i_ = 0;
+
+            
+            //
+            // IMPORTANT: loop starts from 1, not zero!
+            //
+            for(i=1; i<=ng+nh; i++)
+            {
+                v = fi[i];
+                fupd = 0;
+                gupd = 0;
+                if( i<=ng )
+                {
+                    
+                    //
+                    // Equality constraint
+                    //
+                    minnlcequalitypenaltyfunction(v*rho, ref p, ref dp, ref d2p, _params);
+                    fupd = fupd+p/rho;
+                    gupd = gupd+dp;
+                    fupd = fupd-nunlc[i-1]*v;
+                    gupd = gupd-nunlc[i-1];
+                }
+                else
+                {
+                    
+                    //
+                    // Inequality constraint
+                    //
+                    minnlcinequalitypenaltyfunction(-v, stabilizingpoint, ref p, ref dp, ref d2p, _params);
+                    fupd = fupd+p*rho;
+                    gupd = gupd-dp*rho;
+                    minnlcinequalityshiftfunction(-(v*rho)+1, ref p, ref dp, ref d2p, _params);
+                    fupd = fupd+p/rho*nunlc[i-1];
+                    gupd = gupd-dp*nunlc[i-1];
+                }
+                f = f+fupd;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    g[i_] = g[i_] + gupd*j[i,i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function performs actual processing for AUL algorithm. It expects that
+        caller redirects its reverse communication  requests  NeedFiJ/XUpdated  to
+        external user who will provide analytic derivative (or handle reports about
+        progress).
+
+        In case external user does not have analytic derivative, it is responsibility
+        of caller to intercept NeedFiJ request and  replace  it  with  appropriate
+        numerical differentiation scheme.
+
+          -- ALGLIB --
+             Copyright 06.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool auliteration(minnlcstate state,
+            optserv.smoothnessmonitor smonitor,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int nec = 0;
+            int nic = 0;
+            int ng = 0;
+            int nh = 0;
+            int i = 0;
+            int j = 0;
+            int outerit = 0;
+            int preccounter = 0;
+            double v = 0;
+            double vv = 0;
+            double p = 0;
+            double dp = 0;
+            double d2p = 0;
+            double v0 = 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.rstateaul.stage>=0 )
+            {
+                n = state.rstateaul.ia[0];
+                nec = state.rstateaul.ia[1];
+                nic = state.rstateaul.ia[2];
+                ng = state.rstateaul.ia[3];
+                nh = state.rstateaul.ia[4];
+                i = state.rstateaul.ia[5];
+                j = state.rstateaul.ia[6];
+                outerit = state.rstateaul.ia[7];
+                preccounter = state.rstateaul.ia[8];
+                v = state.rstateaul.ra[0];
+                vv = state.rstateaul.ra[1];
+                p = state.rstateaul.ra[2];
+                dp = state.rstateaul.ra[3];
+                d2p = state.rstateaul.ra[4];
+                v0 = state.rstateaul.ra[5];
+                v1 = state.rstateaul.ra[6];
+                v2 = state.rstateaul.ra[7];
+            }
+            else
+            {
+                n = -788;
+                nec = 809;
+                nic = 205;
+                ng = -838;
+                nh = 939;
+                i = -526;
+                j = 763;
+                outerit = -541;
+                preccounter = -698;
+                v = -900;
+                vv = -318;
+                p = -940;
+                dp = 1016;
+                d2p = -229;
+                v0 = -536;
+                v1 = 487;
+                v2 = -115;
+            }
+            if( state.rstateaul.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstateaul.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstateaul.stage==2 )
+            {
+                goto lbl_2;
+            }
+            
+            //
+            // Routine body
+            //
+            alglib.ap.assert(state.solvertype==0, "MinNLC: internal error");
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            ng = state.ng;
+            nh = state.nh;
+            
+            //
+            // Prepare scaled problem
+            //
+            apserv.rvectorsetlengthatleast(ref state.scaledbndl, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.scaledbndu, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.scaledcleic, nec+nic, n+1, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.hasbndl[i] )
+                {
+                    state.scaledbndl[i] = state.bndl[i]/state.s[i];
+                }
+                if( state.hasbndu[i] )
+                {
+                    state.scaledbndu[i] = state.bndu[i]/state.s[i];
+                }
+                state.xc[i] = state.xstart[i]/state.s[i];
+            }
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                
+                //
+                // Scale and normalize linear constraints
+                //
+                vv = 0.0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = state.cleic[i,j]*state.s[j];
+                    state.scaledcleic[i,j] = v;
+                    vv = vv+v*v;
+                }
+                vv = Math.Sqrt(vv);
+                state.scaledcleic[i,n] = state.cleic[i,n];
+                if( (double)(vv)>(double)(0) )
+                {
+                    for(j=0; j<=n; j++)
+                    {
+                        state.scaledcleic[i,j] = state.scaledcleic[i,j]/vv;
+                    }
+                }
+            }
+            
+            //
+            // Prepare stopping criteria
+            //
+            minlbfgs.minlbfgssetcond(state.auloptimizer, 0, 0, state.epsx, state.maxits, _params);
+            minlbfgs.minlbfgssetstpmax(state.auloptimizer, state.stpmax, _params);
+            
+            //
+            // Main AUL cycle:
+            // * prepare Lagrange multipliers NuNB/NuLC
+            // * set GammaK (current estimate of Hessian norm) to InitGamma and XKPresent to False
+            //
+            apserv.rvectorsetlengthatleast(ref state.nubc, 2*n, _params);
+            apserv.rvectorsetlengthatleast(ref state.nulc, nec+nic, _params);
+            apserv.rvectorsetlengthatleast(ref state.nunlc, ng+nh, _params);
+            apserv.rvectorsetlengthatleast(ref state.xk, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.gk, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.xk1, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.gk1, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.nubc[2*i+0] = 0.0;
+                state.nubc[2*i+1] = 0.0;
+                if( (state.hasbndl[i] && state.hasbndu[i]) && (double)(state.bndl[i])==(double)(state.bndu[i]) )
+                {
+                    continue;
+                }
+                if( state.hasbndl[i] )
+                {
+                    state.nubc[2*i+0] = state.initialinequalitymultiplier;
+                }
+                if( state.hasbndu[i] )
+                {
+                    state.nubc[2*i+1] = state.initialinequalitymultiplier;
+                }
+            }
+            for(i=0; i<=nec-1; i++)
+            {
+                state.nulc[i] = 0.0;
+            }
+            for(i=0; i<=nic-1; i++)
+            {
+                state.nulc[nec+i] = state.initialinequalitymultiplier;
+            }
+            for(i=0; i<=ng-1; i++)
+            {
+                state.nunlc[i] = 0.0;
+            }
+            for(i=0; i<=nh-1; i++)
+            {
+                state.nunlc[ng+i] = state.initialinequalitymultiplier;
+            }
+            state.gammak = initgamma;
+            state.xkpresent = false;
+            alglib.ap.assert(state.aulitscnt>0, "MinNLC: integrity check failed");
+            clearpreconditioner(state.auloptimizer, _params);
+            outerit = 0;
+        lbl_3:
+            if( outerit>state.aulitscnt-1 )
+            {
+                goto lbl_5;
+            }
+            
+            //
+            // Optimize with current Lagrange multipliers
+            //
+            // NOTE: this code expects and checks that line search ends in the
+            //       point which is used as beginning for the next search. Such
+            //       guarantee is given by MCSRCH function.  L-BFGS  optimizer
+            //       does not formally guarantee it, but it follows same rule.
+            //       Below we a) rely on such property of the optimizer, and b)
+            //       assert that it is true, in order to fail loudly if it is
+            //       not true.
+            //
+            // NOTE: security check for NAN/INF in F/G is responsibility of
+            //       LBFGS optimizer. AUL optimizer checks for NAN/INF only
+            //       when we update Lagrange multipliers.
+            //
+            preccounter = 0;
+            minlbfgs.minlbfgssetxrep(state.auloptimizer, true, _params);
+            minlbfgs.minlbfgsrestartfrom(state.auloptimizer, state.xc, _params);
+        lbl_6:
+            if( !minlbfgs.minlbfgsiteration(state.auloptimizer, _params) )
+            {
+                goto lbl_7;
+            }
+            if( !state.auloptimizer.needfg )
+            {
+                goto lbl_8;
+            }
+            
+            //
+            // Un-scale X, evaluate F/G/H, re-scale Jacobian
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.auloptimizer.x[i]*state.s[i];
+            }
+            state.needfij = true;
+            state.rstateaul.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfij = false;
+            for(i=0; i<=ng+nh; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    state.j[i,j] = state.j[i,j]*state.s[j];
+                }
+            }
+            
+            //
+            // Store data for estimation of Hessian norm:
+            // * current point (re-scaled)
+            // * gradient of the target function (re-scaled, unmodified)
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xk1[i_] = state.auloptimizer.x[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.gk1[i_] = state.j[0,i_];
+            }
+            
+            //
+            // Function being optimized
+            //
+            state.auloptimizer.f = state.fi[0];
+            for(i=0; i<=n-1; i++)
+            {
+                state.auloptimizer.g[i] = state.j[0,i];
+            }
+            
+            //
+            // Send information to OptGuard monitor
+            //
+            optserv.smoothnessmonitorenqueuepoint(smonitor, state.auloptimizer.d, state.auloptimizer.stp, state.auloptimizer.x, state.fi, state.j, _params);
+            
+            //
+            // Penalty for violation of boundary/linear/nonlinear constraints
+            //
+            penaltybc(state.auloptimizer.x, state.scaledbndl, state.hasbndl, state.scaledbndu, state.hasbndu, state.nubc, n, state.rho, state.stabilizingpoint, ref state.auloptimizer.f, state.auloptimizer.g, _params);
+            penaltylc(state.auloptimizer.x, state.scaledcleic, state.nulc, n, nec, nic, state.rho, state.stabilizingpoint, ref state.auloptimizer.f, state.auloptimizer.g, _params);
+            penaltynlc(state.fi, state.j, state.nunlc, n, ng, nh, state.rho, state.stabilizingpoint, ref state.auloptimizer.f, state.auloptimizer.g, _params);
+            
+            //
+            // Forward termination request if needed
+            //
+            if( state.userterminationneeded )
+            {
+                minlbfgs.minlbfgsrequesttermination(state.auloptimizer, _params);
+            }
+            
+            //
+            // To optimizer
+            //
+            goto lbl_6;
+        lbl_8:
+            if( !state.auloptimizer.xupdated )
+            {
+                goto lbl_10;
+            }
+            
+            //
+            // Report current point (if needed)
+            //
+            if( !state.xrep )
+            {
+                goto lbl_12;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.auloptimizer.x[i]*state.s[i];
+            }
+            state.f = state.auloptimizer.f;
+            state.xupdated = true;
+            state.rstateaul.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.xupdated = false;
+        lbl_12:
+            
+            //
+            // Send information to OptGuard monitor
+            //
+            optserv.smoothnessmonitorfinalizelinesearch(smonitor, _params);
+            optserv.smoothnessmonitorstartlinesearch(smonitor, state.auloptimizer.x, state.fi, state.j, _params);
+            
+            //
+            // Forward termination request if needed
+            //
+            if( state.userterminationneeded )
+            {
+                minlbfgs.minlbfgsrequesttermination(state.auloptimizer, _params);
+            }
+            
+            //
+            // Update constraints violation
+            //
+            optserv.checkbcviolation(state.hasbndl, state.scaledbndl, state.hasbndu, state.scaledbndu, state.auloptimizer.x, n, state.s, false, ref state.repbcerr, ref state.repbcidx, _params);
+            optserv.checklcviolation(state.scaledcleic, state.lcsrcidx, nec, nic, state.auloptimizer.x, n, ref state.replcerr, ref state.replcidx, _params);
+            optserv.checknlcviolation(state.fi, ng, nh, ref state.repnlcerr, ref state.repnlcidx, _params);
+            
+            //
+            // Update GammaK
+            //
+            if( state.xkpresent )
+            {
+                
+                //
+                // XK/GK store beginning of current line search, and XK1/GK1
+                // store data for the end of the line search:
+                // * first, we Assert() that XK1 (last point where function
+                //   was evaluated) is same as AULOptimizer.X (what is
+                //   reported by RComm interface
+                // * calculate step length V2.
+                //
+                // If V2>HessEstTol, then:
+                // * calculate V0 - directional derivative at XK,
+                //   and V1 - directional derivative at XK1
+                // * set GammaK to Max(GammaK, |V1-V0|/V2)
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    alglib.ap.assert((double)(Math.Abs(state.auloptimizer.x[i]-state.xk1[i]))<=(double)(100*math.machineepsilon) || !(math.isfinite(state.auloptimizer.x[i]) && math.isfinite(state.xk1[i])), "MinNLC: integrity check failed, unexpected behavior of LBFGS optimizer");
+                }
+                v2 = 0.0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v2 = v2+math.sqr(state.xk[i]-state.xk1[i]);
+                }
+                v2 = Math.Sqrt(v2);
+                if( (double)(v2)>(double)(hessesttol) )
+                {
+                    v0 = 0.0;
+                    v1 = 0.0;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        v = (state.xk[i]-state.xk1[i])/v2;
+                        v0 = v0+state.gk[i]*v;
+                        v1 = v1+state.gk1[i]*v;
+                    }
+                    state.gammak = Math.Max(state.gammak, Math.Abs(v1-v0)/v2);
+                }
+            }
+            else
+            {
+                
+                //
+                // Beginning of the first line search, XK is not yet initialized.
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.xk[i_] = state.xk1[i_];
+                }
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.gk[i_] = state.gk1[i_];
+                }
+                state.xkpresent = true;
+            }
+            
+            //
+            // Update preconsitioner using current GammaK
+            //
+            updatepreconditioner(state.prectype, state.updatefreq, ref preccounter, state.auloptimizer, state.auloptimizer.x, state.rho, state.gammak, state.scaledbndl, state.hasbndl, state.scaledbndu, state.hasbndu, state.nubc, state.scaledcleic, state.nulc, state.fi, state.j, state.nunlc, ref state.bufd, ref state.bufc, ref state.bufw, ref state.bufz, ref state.tmp0, n, nec, nic, ng, nh, _params);
+            goto lbl_6;
+        lbl_10:
+            alglib.ap.assert(false, "MinNLC: integrity check failed");
+            goto lbl_6;
+        lbl_7:
+            minlbfgs.minlbfgsresultsbuf(state.auloptimizer, ref state.xc, state.aulreport, _params);
+            state.repinneriterationscount = state.repinneriterationscount+state.aulreport.iterationscount;
+            state.repnfev = state.repnfev+state.aulreport.nfev;
+            state.repterminationtype = state.aulreport.terminationtype;
+            apserv.inc(ref state.repouteriterationscount, _params);
+            if( state.repterminationtype<=0 || state.repterminationtype==8 )
+            {
+                goto lbl_5;
+            }
+            
+            //
+            // 1. Evaluate F/J
+            // 2. Check for NAN/INF in F/J: we just calculate sum of their
+            //    components, it should be enough to reduce vector/matrix to
+            //    just one value which either "normal" (all summands were "normal")
+            //    or NAN/INF (at least one summand was NAN/INF).
+            // 3. Update Lagrange multipliers
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.xc[i]*state.s[i];
+            }
+            state.needfij = true;
+            state.rstateaul.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.needfij = false;
+            v = 0.0;
+            for(i=0; i<=ng+nh; i++)
+            {
+                v = 0.1*v+state.fi[i];
+                for(j=0; j<=n-1; j++)
+                {
+                    v = 0.1*v+state.j[i,j];
+                }
+            }
+            if( !math.isfinite(v) )
+            {
+                
+                //
+                // Abnormal termination - infinities in function/gradient
+                //
+                state.repterminationtype = -8;
+                result = false;
+                return result;
+            }
+            for(i=0; i<=ng+nh; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    state.j[i,j] = state.j[i,j]*state.s[j];
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Process coefficients corresponding to equality-type
+                // constraints.
+                //
+                if( (state.hasbndl[i] && state.hasbndu[i]) && (double)(state.bndl[i])==(double)(state.bndu[i]) )
+                {
+                    minnlcequalitypenaltyfunction((state.xc[i]-state.scaledbndl[i])*state.rho, ref p, ref dp, ref d2p, _params);
+                    state.nubc[2*i+0] = apserv.boundval(state.nubc[2*i+0]-dp, -maxlagmult, maxlagmult, _params);
+                    continue;
+                }
+                
+                //
+                // Process coefficients corresponding to inequality-type
+                // constraints. These coefficients have limited growth/decay
+                // per iteration which helps to stabilize algorithm.
+                //
+                alglib.ap.assert((double)(aulmaxgrowth)>(double)(1.0), "MinNLC: integrity error");
+                if( state.hasbndl[i] )
+                {
+                    minnlcinequalityshiftfunction((state.xc[i]-state.scaledbndl[i])*state.rho+1, ref p, ref dp, ref d2p, _params);
+                    v = Math.Abs(dp);
+                    v = Math.Min(v, aulmaxgrowth);
+                    v = Math.Max(v, 1/aulmaxgrowth);
+                    state.nubc[2*i+0] = apserv.boundval(state.nubc[2*i+0]*v, -maxlagmult, maxlagmult, _params);
+                }
+                if( state.hasbndu[i] )
+                {
+                    minnlcinequalityshiftfunction((state.scaledbndu[i]-state.xc[i])*state.rho+1, ref p, ref dp, ref d2p, _params);
+                    v = Math.Abs(dp);
+                    v = Math.Min(v, aulmaxgrowth);
+                    v = Math.Max(v, 1/aulmaxgrowth);
+                    state.nubc[2*i+1] = apserv.boundval(state.nubc[2*i+1]*v, -maxlagmult, maxlagmult, _params);
+                }
+            }
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.scaledcleic[i,i_]*state.xc[i_];
+                }
+                v = v-state.scaledcleic[i,n];
+                if( i<nec )
+                {
+                    minnlcequalitypenaltyfunction(v*state.rho, ref p, ref dp, ref d2p, _params);
+                    state.nulc[i] = apserv.boundval(state.nulc[i]-dp, -maxlagmult, maxlagmult, _params);
+                }
+                else
+                {
+                    minnlcinequalityshiftfunction(-(v*state.rho)+1, ref p, ref dp, ref d2p, _params);
+                    v = Math.Abs(dp);
+                    v = Math.Min(v, aulmaxgrowth);
+                    v = Math.Max(v, 1/aulmaxgrowth);
+                    state.nulc[i] = apserv.boundval(state.nulc[i]*v, -maxlagmult, maxlagmult, _params);
+                }
+            }
+            for(i=1; i<=ng+nh; i++)
+            {
+                
+                //
+                // NOTE: loop index must start from 1, not zero!
+                //
+                v = state.fi[i];
+                if( i<=ng )
+                {
+                    minnlcequalitypenaltyfunction(v*state.rho, ref p, ref dp, ref d2p, _params);
+                    state.nunlc[i-1] = apserv.boundval(state.nunlc[i-1]-dp, -maxlagmult, maxlagmult, _params);
+                }
+                else
+                {
+                    minnlcinequalityshiftfunction(-(v*state.rho)+1, ref p, ref dp, ref d2p, _params);
+                    v = Math.Abs(dp);
+                    v = Math.Min(v, aulmaxgrowth);
+                    v = Math.Max(v, 1/aulmaxgrowth);
+                    state.nunlc[i-1] = apserv.boundval(state.nunlc[i-1]*v, -maxlagmult, maxlagmult, _params);
+                }
+            }
+            outerit = outerit+1;
+            goto lbl_3;
+        lbl_5:
+            for(i=0; i<=n-1; i++)
+            {
+                state.xc[i] = state.xc[i]*state.s[i];
+            }
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstateaul.ia[0] = n;
+            state.rstateaul.ia[1] = nec;
+            state.rstateaul.ia[2] = nic;
+            state.rstateaul.ia[3] = ng;
+            state.rstateaul.ia[4] = nh;
+            state.rstateaul.ia[5] = i;
+            state.rstateaul.ia[6] = j;
+            state.rstateaul.ia[7] = outerit;
+            state.rstateaul.ia[8] = preccounter;
+            state.rstateaul.ra[0] = v;
+            state.rstateaul.ra[1] = vv;
+            state.rstateaul.ra[2] = p;
+            state.rstateaul.ra[3] = dp;
+            state.rstateaul.ra[4] = d2p;
+            state.rstateaul.ra[5] = v0;
+            state.rstateaul.ra[6] = v1;
+            state.rstateaul.ra[7] = v2;
+            return result;
+        }
+
+
+        /*************************************************************************
+        Unscales X (converts from scaled variables to original ones), paying special
+        attention to box constraints (output is always feasible; active constraints
+        are mapped to active ones).
+        *************************************************************************/
+        private static void unscale(minnlcstate state,
+            double[] xs,
+            double[] scaledbndl,
+            double[] scaledbndu,
+            double[] xu,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = state.n;
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.hasbndl[i] && xs[i]<=scaledbndl[i] )
+                {
+                    xu[i] = state.bndl[i];
+                    continue;
+                }
+                if( state.hasbndu[i] && xs[i]>=scaledbndu[i] )
+                {
+                    xu[i] = state.bndu[i];
+                    continue;
+                }
+                xu[i] = xs[i]*state.s[i];
+                if( state.hasbndl[i] && xu[i]<state.bndl[i] )
+                {
+                    xu[i] = state.bndl[i];
+                }
+                if( state.hasbndu[i] && xu[i]>state.bndu[i] )
+                {
+                    xu[i] = state.bndu[i];
+                }
+            }
+        }
+
+
+    }
+    public class minbc
+    {
+        /*************************************************************************
+        This object stores nonlinear optimizer state.
+        You should use functions provided by MinBC subpackage to work with this
+        object
+        *************************************************************************/
+        public class minbcstate : apobject
+        {
+            public int nmain;
+            public double epsg;
+            public double epsf;
+            public double epsx;
+            public int maxits;
+            public bool xrep;
+            public double stpmax;
+            public double diffstep;
+            public double[] s;
+            public int prectype;
+            public double[] diagh;
+            public double[] x;
+            public double f;
+            public double[] g;
+            public bool needf;
+            public bool needfg;
+            public bool xupdated;
+            public bool userterminationneeded;
+            public rcommstate rstate;
+            public double[] xc;
+            public double[] ugc;
+            public double[] cgc;
+            public double[] xn;
+            public double[] ugn;
+            public double[] cgn;
+            public double[] xp;
+            public double fc;
+            public double fn;
+            public double fp;
+            public double[] d;
+            public double lastscaledgoodstep;
+            public bool[] hasbndl;
+            public bool[] hasbndu;
+            public double[] bndl;
+            public double[] bndu;
+            public int repiterationscount;
+            public int repnfev;
+            public int repvaridx;
+            public int repterminationtype;
+            public double[] xstart;
+            public double fbase;
+            public double fm2;
+            public double fm1;
+            public double fp1;
+            public double fp2;
+            public double xm1;
+            public double xp1;
+            public double gm1;
+            public double gp1;
+            public double[] tmpprec;
+            public double[] tmp0;
+            public int nfev;
+            public int mcstage;
+            public double stp;
+            public double curstpmax;
+            public double[] work;
+            public linmin.linminstate lstate;
+            public double trimthreshold;
+            public int nonmonotoniccnt;
+            public double[,] bufyk;
+            public double[,] bufsk;
+            public double[] bufrho;
+            public double[] buftheta;
+            public int bufsize;
+            public double teststep;
+            public int smoothnessguardlevel;
+            public optserv.smoothnessmonitor smonitor;
+            public double[] lastscaleused;
+            public double[] invs;
+            public minbcstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                s = new double[0];
+                diagh = new double[0];
+                x = new double[0];
+                g = new double[0];
+                rstate = new rcommstate();
+                xc = new double[0];
+                ugc = new double[0];
+                cgc = new double[0];
+                xn = new double[0];
+                ugn = new double[0];
+                cgn = new double[0];
+                xp = new double[0];
+                d = new double[0];
+                hasbndl = new bool[0];
+                hasbndu = new bool[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                xstart = new double[0];
+                tmpprec = new double[0];
+                tmp0 = new double[0];
+                work = new double[0];
+                lstate = new linmin.linminstate();
+                bufyk = new double[0,0];
+                bufsk = new double[0,0];
+                bufrho = new double[0];
+                buftheta = new double[0];
+                smonitor = new optserv.smoothnessmonitor();
+                lastscaleused = new double[0];
+                invs = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                minbcstate _result = new minbcstate();
+                _result.nmain = nmain;
+                _result.epsg = epsg;
+                _result.epsf = epsf;
+                _result.epsx = epsx;
+                _result.maxits = maxits;
+                _result.xrep = xrep;
+                _result.stpmax = stpmax;
+                _result.diffstep = diffstep;
+                _result.s = (double[])s.Clone();
+                _result.prectype = prectype;
+                _result.diagh = (double[])diagh.Clone();
+                _result.x = (double[])x.Clone();
+                _result.f = f;
+                _result.g = (double[])g.Clone();
+                _result.needf = needf;
+                _result.needfg = needfg;
+                _result.xupdated = xupdated;
+                _result.userterminationneeded = userterminationneeded;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.xc = (double[])xc.Clone();
+                _result.ugc = (double[])ugc.Clone();
+                _result.cgc = (double[])cgc.Clone();
+                _result.xn = (double[])xn.Clone();
+                _result.ugn = (double[])ugn.Clone();
+                _result.cgn = (double[])cgn.Clone();
+                _result.xp = (double[])xp.Clone();
+                _result.fc = fc;
+                _result.fn = fn;
+                _result.fp = fp;
+                _result.d = (double[])d.Clone();
+                _result.lastscaledgoodstep = lastscaledgoodstep;
+                _result.hasbndl = (bool[])hasbndl.Clone();
+                _result.hasbndu = (bool[])hasbndu.Clone();
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.repiterationscount = repiterationscount;
+                _result.repnfev = repnfev;
+                _result.repvaridx = repvaridx;
+                _result.repterminationtype = repterminationtype;
+                _result.xstart = (double[])xstart.Clone();
+                _result.fbase = fbase;
+                _result.fm2 = fm2;
+                _result.fm1 = fm1;
+                _result.fp1 = fp1;
+                _result.fp2 = fp2;
+                _result.xm1 = xm1;
+                _result.xp1 = xp1;
+                _result.gm1 = gm1;
+                _result.gp1 = gp1;
+                _result.tmpprec = (double[])tmpprec.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.nfev = nfev;
+                _result.mcstage = mcstage;
+                _result.stp = stp;
+                _result.curstpmax = curstpmax;
+                _result.work = (double[])work.Clone();
+                _result.lstate = (linmin.linminstate)lstate.make_copy();
+                _result.trimthreshold = trimthreshold;
+                _result.nonmonotoniccnt = nonmonotoniccnt;
+                _result.bufyk = (double[,])bufyk.Clone();
+                _result.bufsk = (double[,])bufsk.Clone();
+                _result.bufrho = (double[])bufrho.Clone();
+                _result.buftheta = (double[])buftheta.Clone();
+                _result.bufsize = bufsize;
+                _result.teststep = teststep;
+                _result.smoothnessguardlevel = smoothnessguardlevel;
+                _result.smonitor = (optserv.smoothnessmonitor)smonitor.make_copy();
+                _result.lastscaleused = (double[])lastscaleused.Clone();
+                _result.invs = (double[])invs.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure stores optimization report:
+        * iterationscount           number of iterations
+        * nfev                      number of gradient evaluations
+        * terminationtype           termination type (see below)
+
+        TERMINATION CODES
+
+        terminationtype field contains completion code, which can be:
+          -8    internal integrity control detected  infinite  or  NAN  values  in
+                function/gradient. Abnormal termination signalled.
+          -3    inconsistent constraints.
+           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,
+                X contains best point found so far.
+           8    terminated by user who called minbcrequesttermination(). X contains
+                point which was "current accepted" when  termination  request  was
+                submitted.
+        *************************************************************************/
+        public class minbcreport : apobject
+        {
+            public int iterationscount;
+            public int nfev;
+            public int varidx;
+            public int terminationtype;
+            public minbcreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                minbcreport _result = new minbcreport();
+                _result.iterationscount = iterationscount;
+                _result.nfev = nfev;
+                _result.varidx = varidx;
+                _result.terminationtype = terminationtype;
+                return _result;
+            }
+        };
+
+
+
+
+        public const double gtol = 0.4;
+        public const double maxnonmonotoniclen = 1.0E-5;
+        public const double initialdecay = 0.5;
+        public const double mindecay = 0.1;
+        public const double decaycorrection = 0.8;
+
+
+        /*************************************************************************
+                             BOX CONSTRAINED OPTIMIZATION
+                  WITH FAST ACTIVATION OF MULTIPLE BOX CONSTRAINTS
+
+        DESCRIPTION:
+        The  subroutine  minimizes  function   F(x) of N arguments subject  to box
+        constraints (with some of box constraints actually being equality ones).
+
+        This optimizer uses algorithm similar to that of MinBLEIC (optimizer  with
+        general linear constraints), but presence of box-only  constraints  allows
+        us to use faster constraint activation strategies. On large-scale problems,
+        with multiple constraints active at the solution, this  optimizer  can  be
+        several times faster than BLEIC.
+
+        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 BC 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 MinBCCreate() call
+
+        2. USer adds box constraints by calling MinBCSetBC() function.
+
+        3. User sets stopping conditions with MinBCSetCond().
+
+        4. User calls MinBCOptimize() function which takes algorithm  state and
+           pointer (delegate, etc.) to callback function which calculates F/G.
+
+        5. User calls MinBCResults() to get solution
+
+        6. Optionally user may call MinBCRestartFrom() to solve another problem
+           with same N but another starting point.
+           MinBCRestartFrom() 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 minbccreate(int n,
+            double[] x,
+            minbcstate state,
+            alglib.xparams _params)
+        {
+            double[,] c = new double[0,0];
+            int[] ct = new int[0];
+
+            alglib.ap.assert(n>=1, "MinBCCreate: N<1");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinBCCreate: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "MinBCCreate: X contains infinite or NaN values!");
+            minbcinitinternal(n, x, 0.0, state, _params);
+        }
+
+
+        /*************************************************************************
+        The subroutine is finite difference variant of MinBCCreate().  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 MinBCCreate() in  order  to  get
+        more information about creation of BC 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 MinBCSetScale() 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 minbccreatef(int n,
+            double[] x,
+            double diffstep,
+            minbcstate state,
+            alglib.xparams _params)
+        {
+            double[,] c = new double[0,0];
+            int[] ct = new int[0];
+
+            alglib.ap.assert(n>=1, "MinBCCreateF: N<1");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinBCCreateF: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "MinBCCreateF: X contains infinite or NaN values!");
+            alglib.ap.assert(math.isfinite(diffstep), "MinBCCreateF: DiffStep is infinite or NaN!");
+            alglib.ap.assert((double)(diffstep)>(double)(0), "MinBCCreateF: DiffStep is non-positive!");
+            minbcinitinternal(n, x, diffstep, state, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets boundary constraints for BC optimizer.
+
+        Boundary constraints are inactive by default (after initial creation).
+        They are preserved after algorithm restart with MinBCRestartFrom().
+
+        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 minbcsetbc(minbcstate state,
+            double[] bndl,
+            double[] bndu,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = state.nmain;
+            alglib.ap.assert(alglib.ap.len(bndl)>=n, "MinBCSetBC: Length(BndL)<N");
+            alglib.ap.assert(alglib.ap.len(bndu)>=n, "MinBCSetBC: Length(BndU)<N");
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(bndl[i]) || Double.IsNegativeInfinity(bndl[i]), "MinBCSetBC: BndL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(bndu[i]) || Double.IsPositiveInfinity(bndu[i]), "MinBCSetBC: BndL contains NAN or -INF");
+                state.bndl[i] = bndl[i];
+                state.hasbndl[i] = math.isfinite(bndl[i]);
+                state.bndu[i] = bndu[i];
+                state.hasbndu[i] = math.isfinite(bndu[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets stopping conditions for the optimizer.
+
+        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 MinBCSetScale()
+            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 - step vector, dx=X(k+1)-X(k)
+                        * s - scaling coefficients set by MinBCSetScale()
+            MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                        iterations is unlimited.
+
+        Passing EpsG=0, EpsF=0 and EpsX=0 and MaxIts=0 (simultaneously) will lead
+        to automatic stopping criterion selection.
+
+        NOTE: when SetCond() called with non-zero MaxIts, BC solver may perform
+              slightly more than MaxIts iterations. I.e., MaxIts  sets  non-strict
+              limit on iterations count.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbcsetcond(minbcstate state,
+            double epsg,
+            double epsf,
+            double epsx,
+            int maxits,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(epsg), "MinBCSetCond: EpsG is not finite number");
+            alglib.ap.assert((double)(epsg)>=(double)(0), "MinBCSetCond: negative EpsG");
+            alglib.ap.assert(math.isfinite(epsf), "MinBCSetCond: EpsF is not finite number");
+            alglib.ap.assert((double)(epsf)>=(double)(0), "MinBCSetCond: negative EpsF");
+            alglib.ap.assert(math.isfinite(epsx), "MinBCSetCond: EpsX is not finite number");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "MinBCSetCond: negative EpsX");
+            alglib.ap.assert(maxits>=0, "MinBCSetCond: 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 BC 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  BC  too)  scaling is NOT a form of
+        preconditioning. It just  affects  stopping  conditions.  You  should  set
+        preconditioner  by  separate  call  to  one  of  the  MinBCSetPrec...()
+        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 minbcsetscale(minbcstate state,
+            double[] s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(s)>=state.nmain, "MinBCSetScale: Length(S)<N");
+            for(i=0; i<=state.nmain-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(s[i]), "MinBCSetScale: S contains infinite or NAN elements");
+                alglib.ap.assert((double)(s[i])!=(double)(0), "MinBCSetScale: S contains zero elements");
+                state.s[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 minbcsetprecdefault(minbcstate state,
+            alglib.xparams _params)
+        {
+            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 minbcsetprecdiag(minbcstate state,
+            double[] d,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(d)>=state.nmain, "MinBCSetPrecDiag: D is too short");
+            for(i=0; i<=state.nmain-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(d[i]), "MinBCSetPrecDiag: D contains infinite or NAN elements");
+                alglib.ap.assert((double)(d[i])>(double)(0), "MinBCSetPrecDiag: D contains non-positive elements");
+            }
+            apserv.rvectorsetlengthatleast(ref state.diagh, state.nmain, _params);
+            state.prectype = 2;
+            for(i=0; i<=state.nmain-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  MinBCSetScale()
+        call  (before  or after MinBCSetPrecScale() 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 minbcsetprecscale(minbcstate state,
+            alglib.xparams _params)
+        {
+            state.prectype = 3;
+        }
+
+
+        /*************************************************************************
+        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 MinBCOptimize().
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbcsetxrep(minbcstate state,
+            bool needxrep,
+            alglib.xparams _params)
+        {
+            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  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 minbcsetstpmax(minbcstate state,
+            double stpmax,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(stpmax), "MinBCSetStpMax: StpMax is not finite!");
+            alglib.ap.assert((double)(stpmax)>=(double)(0), "MinBCSetStpMax: 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  MinBCCreate() for analytical gradient or  MinBCCreateF()
+           for numerical differentiation) you should choose appropriate variant of
+           MinBCOptimize() - one  which  accepts  function  AND gradient or one
+           which accepts function ONLY.
+
+           Be careful to choose variant of MinBCOptimize() which corresponds to
+           your optimization scheme! Table below lists different  combinations  of
+           callback (function/gradient) passed to MinBCOptimize()  and specific
+           function used to create optimizer.
+
+
+                             |         USER PASSED TO MinBCOptimize()
+           CREATED WITH      |  function only   |  function and gradient
+           ------------------------------------------------------------
+           MinBCCreateF()    |     works               FAILS
+           MinBCCreate()     |     FAILS               works
+
+           Here "FAIL" denotes inappropriate combinations  of  optimizer  creation
+           function  and  MinBCOptimize()  version.   Attemps   to   use   such
+           combination (for  example,  to  create optimizer with MinBCCreateF()
+           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 minbciteration(minbcstate state,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int freezeidx = 0;
+            double freezeval = 0;
+            double scaleddnorm = 0;
+            int n = 0;
+            int m = 0;
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double vv = 0;
+            double v0 = 0;
+            bool b = new bool();
+            int mcinfo = 0;
+            int itidx = 0;
+            double ginit = 0;
+            double gdecay = 0;
+            bool activationstatus = new bool();
+            double activationstep = 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 )
+            {
+                freezeidx = state.rstate.ia[0];
+                n = state.rstate.ia[1];
+                m = state.rstate.ia[2];
+                i = state.rstate.ia[3];
+                j = state.rstate.ia[4];
+                mcinfo = state.rstate.ia[5];
+                itidx = state.rstate.ia[6];
+                b = state.rstate.ba[0];
+                activationstatus = state.rstate.ba[1];
+                freezeval = state.rstate.ra[0];
+                scaleddnorm = state.rstate.ra[1];
+                v = state.rstate.ra[2];
+                vv = state.rstate.ra[3];
+                v0 = state.rstate.ra[4];
+                ginit = state.rstate.ra[5];
+                gdecay = state.rstate.ra[6];
+                activationstep = state.rstate.ra[7];
+            }
+            else
+            {
+                freezeidx = 359;
+                n = -58;
+                m = -919;
+                i = -909;
+                j = 81;
+                mcinfo = 255;
+                itidx = 74;
+                b = false;
+                activationstatus = true;
+                freezeval = 205;
+                scaleddnorm = -838;
+                v = 939;
+                vv = -526;
+                v0 = 763;
+                ginit = -541;
+                gdecay = -698;
+                activationstep = -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;
+            }
+            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;
+            }
+            if( state.rstate.stage==17 )
+            {
+                goto lbl_17;
+            }
+            if( state.rstate.stage==18 )
+            {
+                goto lbl_18;
+            }
+            if( state.rstate.stage==19 )
+            {
+                goto lbl_19;
+            }
+            if( state.rstate.stage==20 )
+            {
+                goto lbl_20;
+            }
+            if( state.rstate.stage==21 )
+            {
+                goto lbl_21;
+            }
+            if( state.rstate.stage==22 )
+            {
+                goto lbl_22;
+            }
+            if( state.rstate.stage==23 )
+            {
+                goto lbl_23;
+            }
+            if( state.rstate.stage==24 )
+            {
+                goto lbl_24;
+            }
+            if( state.rstate.stage==25 )
+            {
+                goto lbl_25;
+            }
+            if( state.rstate.stage==26 )
+            {
+                goto lbl_26;
+            }
+            if( state.rstate.stage==27 )
+            {
+                goto lbl_27;
+            }
+            if( state.rstate.stage==28 )
+            {
+                goto lbl_28;
+            }
+            if( state.rstate.stage==29 )
+            {
+                goto lbl_29;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // Algorithm parameters:
+            // * M          number of L-BFGS corrections.
+            //              This coefficient remains fixed during iterations.
+            // * GDecay     desired decrease of constrained gradient during L-BFGS iterations.
+            //              This coefficient is decreased after each L-BFGS round until
+            //              it reaches minimum decay.
+            //
+            m = Math.Min(5, state.nmain);
+            gdecay = initialdecay;
+            
+            //
+            // Init
+            //
+            n = state.nmain;
+            for(i=0; i<=n-1; i++)
+            {
+                state.xc[i] = state.xstart[i];
+            }
+            if( !optserv.enforceboundaryconstraints(state.xc, state.bndl, state.hasbndl, state.bndu, state.hasbndu, n, 0, _params) )
+            {
+                
+                //
+                // Inconsistent constraints
+                //
+                state.repterminationtype = -3;
+                result = false;
+                return result;
+            }
+            state.userterminationneeded = false;
+            state.repterminationtype = 0;
+            state.repiterationscount = 0;
+            state.repnfev = 0;
+            state.repvaridx = -1;
+            apserv.rmatrixsetlengthatleast(ref state.bufyk, m+1, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.bufsk, m+1, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.bufrho, m, _params);
+            apserv.rvectorsetlengthatleast(ref state.buftheta, m, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmp0, n, _params);
+            optserv.smoothnessmonitorinit(state.smonitor, n, 1, state.smoothnessguardlevel>0, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.lastscaleused[i] = state.s[i];
+                state.invs[i] = 1/state.s[i];
+            }
+            
+            //
+            // Fill TmpPrec with current preconditioner
+            //
+            apserv.rvectorsetlengthatleast(ref state.tmpprec, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.prectype==2 )
+                {
+                    state.tmpprec[i] = 1/state.diagh[i];
+                    continue;
+                }
+                if( state.prectype==3 )
+                {
+                    state.tmpprec[i] = math.sqr(state.s[i]);
+                    continue;
+                }
+                state.tmpprec[i] = 1;
+            }
+            
+            //
+            //  Check correctness of user-supplied gradient
+            //
+            clearrequestfields(state, _params);
+            if( !((double)(state.diffstep)==(double)(0) && (double)(state.teststep)>(double)(0)) )
+            {
+                goto lbl_30;
+            }
+        lbl_32:
+            if( !optserv.smoothnessmonitorcheckgradientatx0(state.smonitor, state.xc, state.s, state.bndl, state.bndu, true, state.teststep, _params) )
+            {
+                goto lbl_33;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.smonitor.x[i];
+            }
+            state.needfg = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfg = false;
+            state.smonitor.fi[0] = state.f;
+            for(i=0; i<=n-1; i++)
+            {
+                state.smonitor.j[0,i] = state.g[i];
+            }
+            goto lbl_32;
+        lbl_33:
+        lbl_30:
+            
+            //
+            // Main cycle of BC-PG algorithm
+            //
+            state.repterminationtype = 0;
+            state.lastscaledgoodstep = 0;
+            state.nonmonotoniccnt = (int)Math.Round(1.5*n)+5;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xc[i_];
+            }
+            clearrequestfields(state, _params);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_34;
+            }
+            state.needfg = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.needfg = false;
+            goto lbl_35;
+        lbl_34:
+            state.needf = true;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.needf = false;
+        lbl_35:
+            state.fc = state.f;
+            optserv.trimprepare(state.f, ref state.trimthreshold, _params);
+            state.repnfev = state.repnfev+1;
+            if( !state.xrep )
+            {
+                goto lbl_36;
+            }
+            
+            //
+            // Report current point
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xc[i_];
+            }
+            state.f = state.fc;
+            state.xupdated = true;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.xupdated = false;
+        lbl_36:
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+        lbl_38:
+            if( false )
+            {
+                goto lbl_39;
+            }
+            
+            //
+            // Steepest descent phase
+            //
+            // (a) calculate unconstrained gradient
+            // (b) check F/G for NAN/INF, abnormally terminate algorithm if needed
+            // (c) perform one steepest descent step, activating only those constraints
+            //     which prevent us from moving outside of box-constrained area
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xc[i_];
+            }
+            clearrequestfields(state, _params);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_40;
+            }
+            
+            //
+            // Analytic gradient
+            //
+            state.needfg = true;
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.needfg = false;
+            goto lbl_41;
+        lbl_40:
+            
+            //
+            // Numerical differentiation
+            //
+            state.needf = true;
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.fbase = state.f;
+            i = 0;
+        lbl_42:
+            if( i>n-1 )
+            {
+                goto lbl_44;
+            }
+            v = state.x[i];
+            b = false;
+            if( state.hasbndl[i] )
+            {
+                b = b || (double)(v-state.diffstep*state.s[i])<(double)(state.bndl[i]);
+            }
+            if( state.hasbndu[i] )
+            {
+                b = b || (double)(v+state.diffstep*state.s[i])>(double)(state.bndu[i]);
+            }
+            if( b )
+            {
+                goto lbl_45;
+            }
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 8;
+            goto lbl_rcomm;
+        lbl_8:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 9;
+            goto lbl_rcomm;
+        lbl_9:
+            state.fp2 = state.f;
+            state.g[i] = (8*(state.fp1-state.fm1)-(state.fp2-state.fm2))/(6*state.diffstep*state.s[i]);
+            goto lbl_46;
+        lbl_45:
+            state.xm1 = v-state.diffstep*state.s[i];
+            state.xp1 = v+state.diffstep*state.s[i];
+            if( state.hasbndl[i] && (double)(state.xm1)<(double)(state.bndl[i]) )
+            {
+                state.xm1 = state.bndl[i];
+            }
+            if( state.hasbndu[i] && (double)(state.xp1)>(double)(state.bndu[i]) )
+            {
+                state.xp1 = state.bndu[i];
+            }
+            state.x[i] = state.xm1;
+            state.rstate.stage = 10;
+            goto lbl_rcomm;
+        lbl_10:
+            state.fm1 = state.f;
+            state.x[i] = state.xp1;
+            state.rstate.stage = 11;
+            goto lbl_rcomm;
+        lbl_11:
+            state.fp1 = state.f;
+            if( (double)(state.xm1)!=(double)(state.xp1) )
+            {
+                state.g[i] = (state.fp1-state.fm1)/(state.xp1-state.xm1);
+            }
+            else
+            {
+                state.g[i] = 0;
+            }
+        lbl_46:
+            state.x[i] = v;
+            i = i+1;
+            goto lbl_42;
+        lbl_44:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_41:
+            state.fc = state.f;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ugc[i_] = state.g[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.cgc[i_] = state.g[i_];
+            }
+            optserv.projectgradientintobc(state.xc, ref state.cgc, state.bndl, state.hasbndl, state.bndu, state.hasbndu, n, 0, _params);
+            ginit = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                ginit = ginit+math.sqr(state.cgc[i]*state.s[i]);
+            }
+            ginit = Math.Sqrt(ginit);
+            if( !math.isfinite(ginit) || !math.isfinite(state.fc) )
+            {
+                
+                //
+                // Abnormal termination - infinities in function/gradient
+                //
+                state.repterminationtype = -8;
+                result = false;
+                return result;
+            }
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            if( (double)(ginit)<=(double)(state.epsg) )
+            {
+                
+                //
+                // Gradient is small enough.
+                // Optimization is terminated
+                //
+                state.repterminationtype = 4;
+                result = false;
+                return result;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.d[i] = -(state.tmpprec[i]*state.cgc[i]);
+            }
+            scaleddnorm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                scaleddnorm = scaleddnorm+math.sqr(state.d[i]/state.s[i]);
+            }
+            scaleddnorm = Math.Sqrt(scaleddnorm);
+            alglib.ap.assert((double)(scaleddnorm)>(double)(0), "MinBC: integrity check failed");
+            if( (double)(state.lastscaledgoodstep)>(double)(0) )
+            {
+                state.stp = state.lastscaledgoodstep/scaleddnorm;
+            }
+            else
+            {
+                state.stp = 1.0/scaleddnorm;
+            }
+            optserv.calculatestepbound(state.xc, state.d, 1.0, state.bndl, state.hasbndl, state.bndu, state.hasbndu, n, 0, ref freezeidx, ref freezeval, ref state.curstpmax, _params);
+            activationstep = state.curstpmax;
+            if( freezeidx<0 || (double)(state.curstpmax)>(double)(1.0E50) )
+            {
+                state.curstpmax = 1.0E50;
+            }
+            if( (double)(state.stpmax)>(double)(0) )
+            {
+                state.curstpmax = Math.Min(state.curstpmax, state.stpmax/scaleddnorm);
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xn[i_] = state.xc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.cgn[i_] = state.cgc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ugn[i_] = state.ugc[i_];
+            }
+            state.fn = state.fc;
+            state.mcstage = 0;
+            optserv.smoothnessmonitorstartlinesearch1u(state.smonitor, state.s, state.invs, state.xn, state.fn, state.ugn, _params);
+            linmin.mcsrch(n, ref state.xn, ref state.fn, ref state.cgn, state.d, ref state.stp, state.curstpmax, gtol, ref mcinfo, ref state.nfev, ref state.work, state.lstate, ref state.mcstage, _params);
+        lbl_47:
+            if( state.mcstage==0 )
+            {
+                goto lbl_48;
+            }
+            
+            //
+            // Copy XN to X, perform on-the-fly correction w.r.t box
+            // constraints (projection onto feasible set).
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.xn[i];
+                if( state.hasbndl[i] && (double)(state.xn[i])<(double)(state.bndl[i]) )
+                {
+                    state.x[i] = state.bndl[i];
+                }
+                if( state.hasbndu[i] && (double)(state.xn[i])>(double)(state.bndu[i]) )
+                {
+                    state.x[i] = state.bndu[i];
+                }
+            }
+            
+            //
+            // Gradient, either user-provided or numerical differentiation
+            //
+            clearrequestfields(state, _params);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_49;
+            }
+            
+            //
+            // Analytic gradient
+            //
+            state.needfg = true;
+            state.rstate.stage = 12;
+            goto lbl_rcomm;
+        lbl_12:
+            state.needfg = false;
+            state.repnfev = state.repnfev+1;
+            goto lbl_50;
+        lbl_49:
+            
+            //
+            // Numerical differentiation
+            //
+            state.needf = true;
+            state.rstate.stage = 13;
+            goto lbl_rcomm;
+        lbl_13:
+            state.fbase = state.f;
+            i = 0;
+        lbl_51:
+            if( i>n-1 )
+            {
+                goto lbl_53;
+            }
+            v = state.x[i];
+            b = false;
+            if( state.hasbndl[i] )
+            {
+                b = b || (double)(v-state.diffstep*state.s[i])<(double)(state.bndl[i]);
+            }
+            if( state.hasbndu[i] )
+            {
+                b = b || (double)(v+state.diffstep*state.s[i])>(double)(state.bndu[i]);
+            }
+            if( b )
+            {
+                goto lbl_54;
+            }
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 14;
+            goto lbl_rcomm;
+        lbl_14:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 15;
+            goto lbl_rcomm;
+        lbl_15:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 16;
+            goto lbl_rcomm;
+        lbl_16:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 17;
+            goto lbl_rcomm;
+        lbl_17:
+            state.fp2 = state.f;
+            state.g[i] = (8*(state.fp1-state.fm1)-(state.fp2-state.fm2))/(6*state.diffstep*state.s[i]);
+            state.repnfev = state.repnfev+4;
+            goto lbl_55;
+        lbl_54:
+            state.xm1 = v-state.diffstep*state.s[i];
+            state.xp1 = v+state.diffstep*state.s[i];
+            if( state.hasbndl[i] && (double)(state.xm1)<(double)(state.bndl[i]) )
+            {
+                state.xm1 = state.bndl[i];
+            }
+            if( state.hasbndu[i] && (double)(state.xp1)>(double)(state.bndu[i]) )
+            {
+                state.xp1 = state.bndu[i];
+            }
+            state.x[i] = state.xm1;
+            state.rstate.stage = 18;
+            goto lbl_rcomm;
+        lbl_18:
+            state.fm1 = state.f;
+            state.x[i] = state.xp1;
+            state.rstate.stage = 19;
+            goto lbl_rcomm;
+        lbl_19:
+            state.fp1 = state.f;
+            if( (double)(state.xm1)!=(double)(state.xp1) )
+            {
+                state.g[i] = (state.fp1-state.fm1)/(state.xp1-state.xm1);
+            }
+            else
+            {
+                state.g[i] = 0;
+            }
+            state.repnfev = state.repnfev+2;
+        lbl_55:
+            state.x[i] = v;
+            i = i+1;
+            goto lbl_51;
+        lbl_53:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_50:
+            
+            //
+            // Back to MCSRCH
+            //
+            optserv.smoothnessmonitorenqueuepoint1u(state.smonitor, state.s, state.invs, state.d, state.stp, state.x, state.f, state.g, _params);
+            optserv.trimfunction(ref state.f, ref state.g, n, state.trimthreshold, _params);
+            state.fn = state.f;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.cgn[i_] = state.g[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ugn[i_] = state.g[i_];
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(state.d[i])==(double)(0) )
+                {
+                    state.cgn[i] = 0;
+                }
+            }
+            linmin.mcsrch(n, ref state.xn, ref state.fn, ref state.cgn, state.d, ref state.stp, state.curstpmax, gtol, ref mcinfo, ref state.nfev, ref state.work, state.lstate, ref state.mcstage, _params);
+            goto lbl_47;
+        lbl_48:
+            optserv.smoothnessmonitorfinalizelinesearch(state.smonitor, _params);
+            v = state.fn;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.1*v+state.ugn[i];
+            }
+            if( !math.isfinite(v) )
+            {
+                
+                //
+                // Abnormal termination - infinities in function/gradient
+                //
+                state.repterminationtype = -8;
+                result = false;
+                return result;
+            }
+            if( mcinfo!=1 && mcinfo!=5 )
+            {
+                
+                //
+                // We can not find step which decreases function value. We have
+                // two possibilities:
+                // (a) numerical properties of the function do not allow us to
+                //     find good step.
+                // (b) we are close to activation of some constraint, and it is
+                //     so close that step which activates it leads to change in
+                //     target function which is smaller than numerical noise.
+                //
+                // Optimization algorithm must be able to handle case (b), because
+                // inability to handle it will cause failure when algorithm
+                // started very close to boundary of the feasible area.
+                //
+                // In order to correctly handle such cases we allow limited amount
+                // of small steps which increase function value.
+                //
+                if( (freezeidx>=0 && (double)(scaleddnorm*state.curstpmax)<=(double)(maxnonmonotoniclen)) && state.nonmonotoniccnt>0 )
+                {
+                    
+                    //
+                    // We enforce non-monotonic step:
+                    // * Stp    := CurStpMax
+                    // * MCINFO := 5
+                    // * XN     := XC+CurStpMax*D
+                    // * non-monotonic counter is decreased
+                    //
+                    // NOTE: UGN/CGN are not updated because step is so short that we assume that
+                    //       GN is approximately equal to GC.
+                    //
+                    state.stp = state.curstpmax;
+                    mcinfo = 5;
+                    v = state.curstpmax;
+                    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.d[i_];
+                    }
+                    state.nonmonotoniccnt = state.nonmonotoniccnt-1;
+                }
+                else
+                {
+                    
+                    //
+                    // Numerical properties of the function does not allow
+                    // us to solve problem. Algorithm is terminated
+                    //
+                    state.repterminationtype = 7;
+                    result = false;
+                    return result;
+                }
+            }
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            alglib.ap.assert(mcinfo!=5 || (double)(state.stp)==(double)(state.curstpmax), "MinBC: integrity check failed");
+            optserv.postprocessboundedstep(ref state.xn, state.xc, state.bndl, state.hasbndl, state.bndu, state.hasbndu, n, 0, freezeidx, freezeval, state.stp, activationstep, _params);
+            state.fp = state.fc;
+            state.fc = state.fn;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xp[i_] = state.xc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xc[i_] = state.xn[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.cgc[i_] = state.cgn[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ugc[i_] = state.ugn[i_];
+            }
+            if( !state.xrep )
+            {
+                goto lbl_56;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xc[i_];
+            }
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 20;
+            goto lbl_rcomm;
+        lbl_20:
+            state.xupdated = false;
+        lbl_56:
+            state.repiterationscount = state.repiterationscount+1;
+            if( mcinfo==1 )
+            {
+                v = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    v = v+math.sqr((state.xc[i]-state.xp[i])/state.s[i]);
+                }
+                v = Math.Sqrt(v);
+                if( (double)(v)<=(double)(state.epsx) )
+                {
+                    
+                    //
+                    // Step is small enough
+                    //
+                    state.repterminationtype = 2;
+                    result = false;
+                    return result;
+                }
+                if( (double)(Math.Abs(state.fp-state.fc))<=(double)(state.epsf*Math.Max(Math.Abs(state.fc), Math.Max(Math.Abs(state.fp), 1.0))) )
+                {
+                    
+                    //
+                    // Function change is small enough
+                    //
+                    state.repterminationtype = 1;
+                    result = false;
+                    return result;
+                }
+            }
+            if( state.maxits>0 && state.repiterationscount>=state.maxits )
+            {
+                
+                //
+                // Iteration counter exceeded limit
+                //
+                state.repterminationtype = 5;
+                result = false;
+                return result;
+            }
+            
+            //
+            // LBFGS stage:
+            // * during LBFGS iterations we activate new constraints, but never
+            //   deactivate already active ones.
+            // * we perform at most N iterations of LBFGS before re-evaluating
+            //   active set and restarting LBFGS.
+            //
+            // About termination:
+            // * LBFGS iterations can be terminated because of two reasons:
+            //   * "termination" - non-zero termination code in RepTerminationType,
+            //     which means that optimization is done
+            //   * "restart" - zero RepTerminationType, which means that we
+            //     have to re-evaluate active set and resume LBFGS stage.
+            // * one more option is "refresh" - to continue LBFGS iterations,
+            //   but with all BFGS updates (Sk/Yk pairs) being dropped;
+            //   it happens after changes in active set
+            //
+            ginit = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                state.cgc[i] = state.ugc[i];
+                if( state.hasbndl[i] && (double)(state.xc[i])==(double)(state.bndl[i]) )
+                {
+                    state.cgc[i] = 0;
+                }
+                if( state.hasbndu[i] && (double)(state.xc[i])==(double)(state.bndu[i]) )
+                {
+                    state.cgc[i] = 0;
+                }
+                ginit = ginit+math.sqr(state.cgc[i]*state.s[i]);
+            }
+            ginit = Math.Sqrt(ginit);
+            state.bufsize = 0;
+            itidx = 0;
+        lbl_58:
+            if( itidx>n-1 )
+            {
+                goto lbl_60;
+            }
+            
+            //
+            // At the beginning of each iteration:
+            // * XC stores current point
+            // * FC stores current function value
+            // * UGC stores current unconstrained gradient
+            // * CGC stores current constrained gradient
+            // * D stores constrained step direction (calculated at this block)
+            //
+            // 1. Calculate search direction D according to L-BFGS algorithm
+            //    using constrained preconditioner to perform inner multiplication.
+            // 2. Evaluate scaled length of direction D; restart LBFGS if D is zero
+            //    (it may be possible that we found minimum, but it is also possible
+            //    that some constraints need deactivation)
+            // 3. If D is non-zero, try to use previous scaled step length as initial estimate for new step.
+            // 4. Calculate bound on step length.
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.work[i_] = state.cgc[i_];
+            }
+            for(i=state.bufsize-1; i>=0; i--)
+            {
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.bufsk[i,i_]*state.work[i_];
+                }
+                state.buftheta[i] = v;
+                vv = v*state.bufrho[i];
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.work[i_] = state.work[i_] - vv*state.bufyk[i,i_];
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.work[i] = state.tmpprec[i]*state.work[i];
+            }
+            for(i=0; i<=state.bufsize-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += state.bufyk[i,i_]*state.work[i_];
+                }
+                vv = state.bufrho[i]*(-v+state.buftheta[i]);
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.work[i_] = state.work[i_] + vv*state.bufsk[i,i_];
+                }
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.d[i_] = -state.work[i_];
+            }
+            b = false;
+            for(i=0; i<=n-1; i++)
+            {
+                b = b || ((state.hasbndl[i] && (double)(state.xc[i])==(double)(state.bndl[i])) && (double)(state.d[i])!=(double)(0));
+                b = b || ((state.hasbndu[i] && (double)(state.xc[i])==(double)(state.bndu[i])) && (double)(state.d[i])!=(double)(0));
+            }
+            alglib.ap.assert(!b, "MinBC: integrity check failed (q)");
+            scaleddnorm = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                scaleddnorm = scaleddnorm+math.sqr(state.d[i]/state.s[i]);
+            }
+            scaleddnorm = Math.Sqrt(scaleddnorm);
+            if( (double)(scaleddnorm)==(double)(0) )
+            {
+                
+                //
+                // Search direction is zero.
+                // Skip back to steepest descent phase.
+                //
+                goto lbl_60;
+            }
+            if( (double)(state.lastscaledgoodstep)>(double)(0) )
+            {
+                state.stp = state.lastscaledgoodstep/scaleddnorm;
+            }
+            else
+            {
+                state.stp = 1.0/scaleddnorm;
+            }
+            state.curstpmax = 1.0E50;
+            if( (double)(state.stpmax)>(double)(0) )
+            {
+                state.curstpmax = Math.Min(state.curstpmax, state.stpmax/scaleddnorm);
+            }
+            
+            //
+            // Minimize G(t) = F(CONSTRAIN(XC + t*D)), with t being scalar, XC and D being vectors.
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xn[i_] = state.xc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.cgn[i_] = state.cgc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ugn[i_] = state.ugc[i_];
+            }
+            state.fn = state.fc;
+            state.mcstage = 0;
+            optserv.smoothnessmonitorstartlinesearch1u(state.smonitor, state.s, state.invs, state.xn, state.fn, state.ugn, _params);
+            linmin.mcsrch(n, ref state.xn, ref state.fn, ref state.cgn, state.d, ref state.stp, state.curstpmax, gtol, ref mcinfo, ref state.nfev, ref state.work, state.lstate, ref state.mcstage, _params);
+        lbl_61:
+            if( state.mcstage==0 )
+            {
+                goto lbl_62;
+            }
+            
+            //
+            // Copy XN to X, perform on-the-fly correction w.r.t box
+            // constraints (projection onto feasible set).
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.xn[i];
+                if( state.hasbndl[i] && (double)(state.xn[i])<=(double)(state.bndl[i]) )
+                {
+                    state.x[i] = state.bndl[i];
+                }
+                if( state.hasbndu[i] && (double)(state.xn[i])>=(double)(state.bndu[i]) )
+                {
+                    state.x[i] = state.bndu[i];
+                }
+            }
+            
+            //
+            // Gradient, either user-provided or numerical differentiation
+            //
+            clearrequestfields(state, _params);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_63;
+            }
+            
+            //
+            // Analytic gradient
+            //
+            state.needfg = true;
+            state.rstate.stage = 21;
+            goto lbl_rcomm;
+        lbl_21:
+            state.needfg = false;
+            state.repnfev = state.repnfev+1;
+            goto lbl_64;
+        lbl_63:
+            
+            //
+            // Numerical differentiation
+            //
+            state.needf = true;
+            state.rstate.stage = 22;
+            goto lbl_rcomm;
+        lbl_22:
+            state.fbase = state.f;
+            i = 0;
+        lbl_65:
+            if( i>n-1 )
+            {
+                goto lbl_67;
+            }
+            v = state.x[i];
+            b = false;
+            if( state.hasbndl[i] )
+            {
+                b = b || (double)(v-state.diffstep*state.s[i])<(double)(state.bndl[i]);
+            }
+            if( state.hasbndu[i] )
+            {
+                b = b || (double)(v+state.diffstep*state.s[i])>(double)(state.bndu[i]);
+            }
+            if( b )
+            {
+                goto lbl_68;
+            }
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 23;
+            goto lbl_rcomm;
+        lbl_23:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 24;
+            goto lbl_rcomm;
+        lbl_24:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 25;
+            goto lbl_rcomm;
+        lbl_25:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 26;
+            goto lbl_rcomm;
+        lbl_26:
+            state.fp2 = state.f;
+            state.g[i] = (8*(state.fp1-state.fm1)-(state.fp2-state.fm2))/(6*state.diffstep*state.s[i]);
+            state.repnfev = state.repnfev+4;
+            goto lbl_69;
+        lbl_68:
+            state.xm1 = v-state.diffstep*state.s[i];
+            state.xp1 = v+state.diffstep*state.s[i];
+            if( state.hasbndl[i] && (double)(state.xm1)<(double)(state.bndl[i]) )
+            {
+                state.xm1 = state.bndl[i];
+            }
+            if( state.hasbndu[i] && (double)(state.xp1)>(double)(state.bndu[i]) )
+            {
+                state.xp1 = state.bndu[i];
+            }
+            state.x[i] = state.xm1;
+            state.rstate.stage = 27;
+            goto lbl_rcomm;
+        lbl_27:
+            state.fm1 = state.f;
+            state.x[i] = state.xp1;
+            state.rstate.stage = 28;
+            goto lbl_rcomm;
+        lbl_28:
+            state.fp1 = state.f;
+            if( (double)(state.xm1)!=(double)(state.xp1) )
+            {
+                state.g[i] = (state.fp1-state.fm1)/(state.xp1-state.xm1);
+            }
+            else
+            {
+                state.g[i] = 0;
+            }
+            state.repnfev = state.repnfev+2;
+        lbl_69:
+            state.x[i] = v;
+            i = i+1;
+            goto lbl_65;
+        lbl_67:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_64:
+            
+            //
+            // Back to MCSRCH
+            //
+            optserv.smoothnessmonitorenqueuepoint1u(state.smonitor, state.s, state.invs, state.d, state.stp, state.x, state.f, state.g, _params);
+            optserv.trimfunction(ref state.f, ref state.g, n, state.trimthreshold, _params);
+            state.fn = state.f;
+            for(i=0; i<=n-1; i++)
+            {
+                state.ugn[i] = state.g[i];
+                state.cgn[i] = state.g[i];
+                if( state.hasbndl[i] && (double)(state.xn[i])<=(double)(state.bndl[i]) )
+                {
+                    state.cgn[i] = 0;
+                }
+                if( state.hasbndu[i] && (double)(state.xn[i])>=(double)(state.bndu[i]) )
+                {
+                    state.cgn[i] = 0;
+                }
+            }
+            linmin.mcsrch(n, ref state.xn, ref state.fn, ref state.cgn, state.d, ref state.stp, state.curstpmax, gtol, ref mcinfo, ref state.nfev, ref state.work, state.lstate, ref state.mcstage, _params);
+            goto lbl_61;
+        lbl_62:
+            optserv.smoothnessmonitorfinalizelinesearch(state.smonitor, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( state.hasbndl[i] && (double)(state.xn[i])<=(double)(state.bndl[i]) )
+                {
+                    state.xn[i] = state.bndl[i];
+                }
+                if( state.hasbndu[i] && (double)(state.xn[i])>=(double)(state.bndu[i]) )
+                {
+                    state.xn[i] = state.bndu[i];
+                }
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.bufsk[state.bufsize,i_] = -state.xc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.bufyk[state.bufsize,i_] = -state.cgc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.bufsk[state.bufsize,i_] = state.bufsk[state.bufsize,i_] + state.xn[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.bufyk[state.bufsize,i_] = state.bufyk[state.bufsize,i_] + state.cgn[i_];
+            }
+            
+            //
+            // Handle special situations:
+            // * check for presence of NAN/INF in function/gradient
+            // * handle failure of line search
+            //
+            v = state.fn;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.1*v+state.ugn[i];
+            }
+            if( !math.isfinite(v) )
+            {
+                
+                //
+                // Abnormal termination - infinities in function/gradient
+                //
+                state.repterminationtype = -8;
+                result = false;
+                return result;
+            }
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            if( mcinfo!=1 )
+            {
+                
+                //
+                // Terminate LBFGS phase
+                //
+                goto lbl_60;
+            }
+            
+            //
+            // Current point is updated:
+            // * move XC/FC/GC to XP/FP/GP
+            // * move XN/FN/GN to XC/FC/GC
+            // * report current point and update iterations counter
+            // * push new pair SK/YK to LBFGS buffer
+            // * update length of the good step
+            //
+            activationstatus = false;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (state.hasbndl[i] && (double)(state.xn[i])==(double)(state.bndl[i])) && (double)(state.xn[i])!=(double)(state.xc[i]) )
+                {
+                    activationstatus = true;
+                }
+                if( (state.hasbndu[i] && (double)(state.xn[i])==(double)(state.bndu[i])) && (double)(state.xn[i])!=(double)(state.xc[i]) )
+                {
+                    activationstatus = true;
+                }
+            }
+            state.fp = state.fc;
+            state.fc = state.fn;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xp[i_] = state.xc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xc[i_] = state.xn[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.cgc[i_] = state.cgn[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.ugc[i_] = state.ugn[i_];
+            }
+            if( !state.xrep )
+            {
+                goto lbl_70;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xc[i_];
+            }
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 29;
+            goto lbl_rcomm;
+        lbl_29:
+            state.xupdated = false;
+        lbl_70:
+            state.repiterationscount = state.repiterationscount+1;
+            if( state.bufsize==m )
+            {
+                
+                //
+                // Buffer is full, shift contents by one row
+                //
+                for(i=0; i<=state.bufsize-1; i++)
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.bufsk[i,i_] = state.bufsk[i+1,i_];
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.bufyk[i,i_] = state.bufyk[i+1,i_];
+                    }
+                }
+                for(i=0; i<=state.bufsize-2; i++)
+                {
+                    state.bufrho[i] = state.bufrho[i+1];
+                    state.buftheta[i] = state.buftheta[i+1];
+                }
+            }
+            else
+            {
+                
+                //
+                // Buffer is not full, increase buffer size by 1
+                //
+                state.bufsize = state.bufsize+1;
+            }
+            v = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                v += state.bufyk[state.bufsize-1,i_]*state.bufsk[state.bufsize-1,i_];
+            }
+            vv = 0.0;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                vv += state.bufyk[state.bufsize-1,i_]*state.bufyk[state.bufsize-1,i_];
+            }
+            if( (double)(v)==(double)(0) || (double)(vv)==(double)(0) )
+            {
+                
+                //
+                // Strange internal error in LBFGS - either YK=0
+                // (which should not have been) or (SK,YK)=0 (again,
+                // unexpected). It should not take place because
+                // MCINFO=1, which signals "good" step. But just
+                // to be sure we have special branch of code which
+                // restarts LBFGS
+                //
+                goto lbl_60;
+            }
+            state.bufrho[state.bufsize-1] = 1/v;
+            alglib.ap.assert(state.bufsize<=m, "MinBC: internal error");
+            v = 0;
+            vv = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr((state.xc[i]-state.xp[i])/state.s[i]);
+                vv = vv+math.sqr(state.xc[i]-state.xp[i]);
+            }
+            updateestimateofgoodstep(ref state.lastscaledgoodstep, Math.Sqrt(v), _params);
+            
+            //
+            // Check MaxIts-based stopping condition.
+            //
+            if( state.maxits>0 && state.repiterationscount>=state.maxits )
+            {
+                state.repterminationtype = 5;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Smooth reset (LBFGS memory model is refreshed) or hard restart:
+            // * LBFGS model is refreshed, if line search was performed with activation of constraints
+            // * algorithm is restarted if scaled gradient decreased below GDecay
+            //
+            if( activationstatus )
+            {
+                state.bufsize = 0;
+                goto lbl_59;
+            }
+            v = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.cgc[i]*state.s[i]);
+            }
+            if( (double)(Math.Sqrt(v))<(double)(gdecay*ginit) )
+            {
+                goto lbl_60;
+            }
+        lbl_59:
+            itidx = itidx+1;
+            goto lbl_58;
+        lbl_60:
+            
+            //
+            // Decrease decay coefficient. Subsequent L-BFGS stages will
+            // have more stringent stopping criteria.
+            //
+            gdecay = Math.Max(gdecay*decaycorrection, mindecay);
+            goto lbl_38;
+        lbl_39:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = freezeidx;
+            state.rstate.ia[1] = n;
+            state.rstate.ia[2] = m;
+            state.rstate.ia[3] = i;
+            state.rstate.ia[4] = j;
+            state.rstate.ia[5] = mcinfo;
+            state.rstate.ia[6] = itidx;
+            state.rstate.ba[0] = b;
+            state.rstate.ba[1] = activationstatus;
+            state.rstate.ra[0] = freezeval;
+            state.rstate.ra[1] = scaleddnorm;
+            state.rstate.ra[2] = v;
+            state.rstate.ra[3] = vv;
+            state.rstate.ra[4] = v0;
+            state.rstate.ra[5] = ginit;
+            state.rstate.ra[6] = gdecay;
+            state.rstate.ra[7] = activationstep;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This  function  activates/deactivates verification  of  the  user-supplied
+        analytic gradient.
+
+        Upon  activation  of  this  option  OptGuard  integrity  checker  performs
+        numerical differentiation of your target function  at  the  initial  point
+        (note: future versions may also perform check  at  the  final  point)  and
+        compares numerical gradient with analytic one provided by you.
+
+        If difference is too large, an error flag is set and optimization  session
+        continues. After optimization session is over, you can retrieve the report
+        which  stores  both  gradients  and  specific  components  highlighted  as
+        suspicious by the OptGuard.
+
+        The primary OptGuard report can be retrieved with minbcoptguardresults().
+
+        IMPORTANT: gradient check is a high-overhead option which  will  cost  you
+                   about 3*N additional function evaluations. In many cases it may
+                   cost as much as the rest of the optimization session.
+                   
+                   YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO
+                   CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY.
+
+        NOTE: unlike previous incarnation of the gradient checking code,  OptGuard
+              does NOT interrupt optimization even if it discovers bad gradient.
+              
+        INPUT PARAMETERS:
+            State       -   structure used to store algorithm state
+            TestStep    -   verification step used for numerical differentiation:
+                            * TestStep=0 turns verification off
+                            * TestStep>0 activates verification
+                            You should carefully choose TestStep. Value  which  is
+                            too large (so large that  function  behavior  is  non-
+                            cubic at this scale) will lead  to  false  alarms. Too
+                            short step will result in rounding  errors  dominating
+                            numerical derivative.
+                            
+                            You may use different step for different parameters by
+                            means of setting scale with minbcsetscale().
+
+        === EXPLANATION ==========================================================                    
+
+        In order to verify gradient algorithm performs following steps:
+          * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i],
+            where X[i] is i-th component of the initial point and S[i] is a  scale
+            of i-th parameter
+          * F(X) is evaluated at these trial points
+          * we perform one more evaluation in the middle point of the interval
+          * we  build  cubic  model using function values and derivatives at trial
+            points and we compare its prediction with actual value in  the  middle
+            point
+
+          -- ALGLIB --
+             Copyright 15.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbcoptguardgradient(minbcstate state,
+            double teststep,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(teststep), "MinBCOptGuardGradient: TestStep contains NaN or INF");
+            alglib.ap.assert((double)(teststep)>=(double)(0), "MinBCOptGuardGradient: invalid argument TestStep(TestStep<0)");
+            state.teststep = teststep;
+        }
+
+
+        /*************************************************************************
+        This  function  activates/deactivates nonsmoothness monitoring  option  of
+        the  OptGuard  integrity  checker. Smoothness  monitor  silently  observes
+        solution process and tries to detect ill-posed problems, i.e. ones with:
+        a) discontinuous target function (non-C0)
+        b) nonsmooth     target function (non-C1)
+
+        Smoothness monitoring does NOT interrupt optimization  even if it suspects
+        that your problem is nonsmooth. It just sets corresponding  flags  in  the
+        OptGuard report which can be retrieved after optimization is over.
+
+        Smoothness monitoring is a moderate overhead option which often adds  less
+        than 1% to the optimizer running time. Thus, you can use it even for large
+        scale problems.
+
+        NOTE: OptGuard does  NOT  guarantee  that  it  will  always  detect  C0/C1
+              continuity violations.
+              
+              First, minor errors are hard to  catch - say, a 0.0001 difference in
+              the model values at two sides of the gap may be due to discontinuity
+              of the model - or simply because the model has changed.
+              
+              Second, C1-violations  are  especially  difficult  to  detect  in  a
+              noninvasive way. The optimizer usually  performs  very  short  steps
+              near the nonsmoothness, and differentiation  usually   introduces  a
+              lot of numerical noise.  It  is  hard  to  tell  whether  some  tiny
+              discontinuity in the slope is due to real nonsmoothness or just  due
+              to numerical noise alone.
+              
+              Our top priority was to avoid false positives, so in some rare cases
+              minor errors may went unnoticed (however, in most cases they can  be
+              spotted with restart from different initial point).
+
+        INPUT PARAMETERS:
+            state   -   algorithm state
+            level   -   monitoring level:
+                        * 0 - monitoring is disabled
+                        * 1 - noninvasive low-overhead monitoring; function values
+                              and/or gradients are recorded, but OptGuard does not
+                              try to perform additional evaluations  in  order  to
+                              get more information about suspicious locations.
+          
+        === EXPLANATION ==========================================================
+
+        One major source of headache during optimization  is  the  possibility  of
+        the coding errors in the target function/constraints (or their gradients).
+        Such  errors   most   often   manifest   themselves  as  discontinuity  or
+        nonsmoothness of the target/constraints.
+
+        Another frequent situation is when you try to optimize something involving
+        lots of min() and max() operations, i.e. nonsmooth target. Although not  a
+        coding error, it is nonsmoothness anyway - and smooth  optimizers  usually
+        stop right after encountering nonsmoothness, well before reaching solution.
+
+        OptGuard integrity checker helps you to catch such situations: it monitors
+        function values/gradients being passed  to  the  optimizer  and  tries  to
+        errors. Upon discovering suspicious pair of points it  raises  appropriate
+        flag (and allows you to continue optimization). When optimization is done,
+        you can study OptGuard result.
+
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbcoptguardsmoothness(minbcstate state,
+            int level,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(level==0 || level==1, "MinBCOptGuardSmoothness: unexpected value of level parameter");
+            state.smoothnessguardlevel = level;
+        }
+
+
+        /*************************************************************************
+        Results of OptGuard integrity check, should be called  after  optimization
+        session is over.
+
+        === PRIMARY REPORT =======================================================
+
+        OptGuard performs several checks which are intended to catch common errors
+        in the implementation of nonlinear function/gradient:
+        * incorrect analytic gradient
+        * discontinuous (non-C0) target functions (constraints)
+        * nonsmooth     (non-C1) target functions (constraints)
+
+        Each of these checks is activated with appropriate function:
+        * minbcoptguardgradient() for gradient verification
+        * minbcoptguardsmoothness() for C0/C1 checks
+
+        Following flags are set when these errors are suspected:
+        * rep.badgradsuspected, and additionally:
+          * rep.badgradvidx for specific variable (gradient element) suspected
+          * rep.badgradxbase, a point where gradient is tested
+          * rep.badgraduser, user-provided gradient  (stored  as  2D  matrix  with
+            single row in order to make  report  structure  compatible  with  more
+            complex optimizers like MinNLC or MinLM)
+          * rep.badgradnum,   reference    gradient    obtained    via   numerical
+            differentiation (stored as  2D matrix with single row in order to make
+            report structure compatible with more complex optimizers  like  MinNLC
+            or MinLM)
+        * rep.nonc0suspected
+        * rep.nonc1suspected
+
+        === ADDITIONAL REPORTS/LOGS ==============================================
+            
+        Several different tests are performed to catch C0/C1 errors, you can  find
+        out specific test signaled error by looking to:
+        * rep.nonc0test0positive, for non-C0 test #0
+        * rep.nonc1test0positive, for non-C1 test #0
+        * rep.nonc1test1positive, for non-C1 test #1
+
+        Additional information (including line search logs)  can  be  obtained  by
+        means of:
+        * minbcoptguardnonc1test0results()
+        * minbcoptguardnonc1test1results()
+        which return detailed error reports, specific points where discontinuities
+        were found, and so on.
+
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            rep     -   generic OptGuard report;  more  detailed  reports  can  be
+                        retrieved with other functions.
+
+        NOTE: false negatives (nonsmooth problems are not identified as  nonsmooth
+              ones) are possible although unlikely.
+              
+              The reason  is  that  you  need  to  make several evaluations around
+              nonsmoothness  in  order  to  accumulate  enough  information  about
+              function curvature. Say, if you start right from the nonsmooth point,
+              optimizer simply won't get enough data to understand what  is  going
+              wrong before it terminates due to abrupt changes in the  derivative.
+              It is also  possible  that  "unlucky"  step  will  move  us  to  the
+              termination too quickly.
+              
+              Our current approach is to have less than 0.1%  false  negatives  in
+              our test examples  (measured  with  multiple  restarts  from  random
+              points), and to have exactly 0% false positives.
+           
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbcoptguardresults(minbcstate state,
+            optguardapi.optguardreport rep,
+            alglib.xparams _params)
+        {
+            optserv.smoothnessmonitorexportreport(state.smonitor, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Detailed results of the OptGuard integrity check for nonsmoothness test #0
+
+        Nonsmoothness (non-C1) test #0 studies  function  values  (not  gradient!)
+        obtained during line searches and monitors  behavior  of  the  directional
+        derivative estimate.
+
+        This test is less powerful than test #1, but it does  not  depend  on  the
+        gradient values and thus it is more robust against artifacts introduced by
+        numerical differentiation.
+
+        Two reports are returned:
+        * a "strongest" one, corresponding  to  line   search  which  had  highest
+          value of the nonsmoothness indicator
+        * a "longest" one, corresponding to line search which  had  more  function
+          evaluations, and thus is more detailed
+
+        In both cases following fields are returned:
+
+        * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+          did not notice anything (in the latter cases fields below are empty).
+        * x0[], d[] - arrays of length N which store initial point  and  direction
+          for line search (d[] can be normalized, but does not have to)
+        * stp[], f[] - arrays of length CNT which store step lengths and  function
+          values at these points; f[i] is evaluated in x0+stp[i]*d.
+        * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+          between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+          with  most  likely  position  of  the  violation  between  stpidxa+1 and
+          stpidxa+2.
+          
+        ==========================================================================
+        = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+        =                   see where C1 continuity is violated.
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            strrep  -   C1 test #0 "strong" report
+            lngrep  -   C1 test #0 "long" report
+            
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbcoptguardnonc1test0results(minbcstate state,
+            optguardapi.optguardnonc1test0report strrep,
+            optguardapi.optguardnonc1test0report lngrep,
+            alglib.xparams _params)
+        {
+            optguardapi.smoothnessmonitorexportc1test0report(state.smonitor.nonc1test0strrep, state.lastscaleused, strrep, _params);
+            optguardapi.smoothnessmonitorexportc1test0report(state.smonitor.nonc1test0lngrep, state.lastscaleused, lngrep, _params);
+        }
+
+
+        /*************************************************************************
+        Detailed results of the OptGuard integrity check for nonsmoothness test #1
+
+        Nonsmoothness (non-C1)  test  #1  studies  individual  components  of  the
+        gradient computed during line search.
+
+        When precise analytic gradient is provided this test is more powerful than
+        test #0  which  works  with  function  values  and  ignores  user-provided
+        gradient.  However,  test  #0  becomes  more   powerful   when   numerical
+        differentiation is employed (in such cases test #1 detects  higher  levels
+        of numerical noise and becomes too conservative).
+
+        This test also tells specific components of the gradient which violate  C1
+        continuity, which makes it more informative than #0, which just tells that
+        continuity is violated.
+
+        Two reports are returned:
+        * a "strongest" one, corresponding  to  line   search  which  had  highest
+          value of the nonsmoothness indicator
+        * a "longest" one, corresponding to line search which  had  more  function
+          evaluations, and thus is more detailed
+
+        In both cases following fields are returned:
+
+        * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+          did not notice anything (in the latter cases fields below are empty).
+        * vidx - is an index of the variable in [0,N) with nonsmooth derivative
+        * x0[], d[] - arrays of length N which store initial point  and  direction
+          for line search (d[] can be normalized, but does not have to)
+        * stp[], g[] - arrays of length CNT which store step lengths and  gradient
+          values at these points; g[i] is evaluated in  x0+stp[i]*d  and  contains
+          vidx-th component of the gradient.
+        * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+          between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+          with  most  likely  position  of  the  violation  between  stpidxa+1 and
+          stpidxa+2.
+          
+        ==========================================================================
+        = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+        =                   see where C1 continuity is violated.
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            strrep  -   C1 test #1 "strong" report
+            lngrep  -   C1 test #1 "long" report
+            
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbcoptguardnonc1test1results(minbcstate state,
+            optguardapi.optguardnonc1test1report strrep,
+            optguardapi.optguardnonc1test1report lngrep,
+            alglib.xparams _params)
+        {
+            optguardapi.smoothnessmonitorexportc1test1report(state.smonitor.nonc1test1strrep, state.lastscaleused, strrep, _params);
+            optguardapi.smoothnessmonitorexportc1test1report(state.smonitor.nonc1test1lngrep, state.lastscaleused, lngrep, _params);
+        }
+
+
+        /*************************************************************************
+        BC 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:
+                        * -8    internal integrity control  detected  infinite or
+                                NAN   values   in   function/gradient.   Abnormal
+                                termination signalled.
+                        * -3   inconsistent constraints.
+                        *  1   relative function improvement is no more than EpsF.
+                        *  2   scaled step is no more than EpsX.
+                        *  4   scaled gradient norm is no more than EpsG.
+                        *  5   MaxIts steps was taken
+                        *  8   terminated by user who called minbcrequesttermination().
+                               X contains point which was "current accepted"  when
+                               termination request was submitted.
+                        More information about fields of this  structure  can  be
+                        found in the comments on MinBCReport datatype.
+           
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbcresults(minbcstate state,
+            ref double[] x,
+            minbcreport rep,
+            alglib.xparams _params)
+        {
+            x = new double[0];
+
+            minbcresultsbuf(state, ref x, rep, _params);
+        }
+
+
+        /*************************************************************************
+        BC results
+
+        Buffered implementation of MinBCResults() 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 minbcresultsbuf(minbcstate state,
+            ref double[] x,
+            minbcreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            if( alglib.ap.len(x)<state.nmain )
+            {
+                x = new double[state.nmain];
+            }
+            rep.iterationscount = state.repiterationscount;
+            rep.nfev = state.repnfev;
+            rep.varidx = state.repvaridx;
+            rep.terminationtype = state.repterminationtype;
+            if( state.repterminationtype>0 )
+            {
+                for(i_=0; i_<=state.nmain-1;i_++)
+                {
+                    x[i_] = state.xc[i_];
+                }
+            }
+            else
+            {
+                for(i=0; i<=state.nmain-1; i++)
+                {
+                    x[i] = Double.NaN;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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 MinBCCreate call.
+            X       -   new starting point.
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbcrestartfrom(minbcstate state,
+            double[] x,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i_ = 0;
+
+            n = state.nmain;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinBCRestartFrom: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "MinBCRestartFrom: 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[6+1];
+            state.rstate.ba = new bool[1+1];
+            state.rstate.ra = new double[7+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine submits request for termination of running  optimizer.  It
+        should be called from user-supplied callback when user decides that it  is
+        time to "smoothly" terminate optimization process.  As  result,  optimizer
+        stops at point which was "current accepted" when termination  request  was
+        submitted and returns error code 8 (successful termination).
+
+        INPUT PARAMETERS:
+            State   -   optimizer structure
+
+        NOTE: after  request  for  termination  optimizer  may   perform   several
+              additional calls to user-supplied callbacks. It does  NOT  guarantee
+              to stop immediately - it just guarantees that these additional calls
+              will be discarded later.
+
+        NOTE: calling this function on optimizer which is NOT running will have no
+              effect.
+              
+        NOTE: multiple calls to this function are possible. First call is counted,
+              subsequent calls are silently ignored.
+
+          -- ALGLIB --
+             Copyright 08.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minbcrequesttermination(minbcstate state,
+            alglib.xparams _params)
+        {
+            state.userterminationneeded = true;
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forget to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(minbcstate state,
+            alglib.xparams _params)
+        {
+            state.needf = false;
+            state.needfg = false;
+            state.xupdated = false;
+        }
+
+
+        /*************************************************************************
+        Internal initialization subroutine.
+        *************************************************************************/
+        private static void minbcinitinternal(int n,
+            double[] x,
+            double diffstep,
+            minbcstate state,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double[,] c = new double[0,0];
+            int[] ct = new int[0];
+
+            
+            //
+            // Initialize
+            //
+            state.teststep = 0;
+            state.smoothnessguardlevel = 0;
+            optserv.smoothnessmonitorinit(state.smonitor, 0, 0, false, _params);
+            state.nmain = n;
+            state.diffstep = diffstep;
+            apserv.rvectorsetlengthatleast(ref state.bndl, n, _params);
+            apserv.bvectorsetlengthatleast(ref state.hasbndl, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.bndu, n, _params);
+            apserv.bvectorsetlengthatleast(ref state.hasbndu, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.xstart, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.xc, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.cgc, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.ugc, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.xn, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.cgn, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.ugn, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.xp, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.d, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.s, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.invs, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.lastscaleused, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.x, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.g, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.work, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.bndl[i] = Double.NegativeInfinity;
+                state.hasbndl[i] = false;
+                state.bndu[i] = Double.PositiveInfinity;
+                state.hasbndu[i] = false;
+                state.s[i] = 1.0;
+                state.invs[i] = 1.0;
+                state.lastscaleused[i] = 1.0;
+            }
+            minbcsetcond(state, 0.0, 0.0, 0.0, 0, _params);
+            minbcsetxrep(state, false, _params);
+            minbcsetstpmax(state, 0.0, _params);
+            minbcsetprecdefault(state, _params);
+            minbcrestartfrom(state, x, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine updates estimate of the good step length given:
+        1) previous estimate
+        2) new length of the good step
+
+        It makes sure that estimate does not change too rapidly - ratio of new and
+        old estimates will be at least 0.01, at most 100.0
+
+        In case previous estimate of good step is zero (no estimate), new estimate
+        is used unconditionally.
+
+          -- ALGLIB --
+             Copyright 16.01.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void updateestimateofgoodstep(ref double estimate,
+            double newstep,
+            alglib.xparams _params)
+        {
+            if( (double)(estimate)==(double)(0) )
+            {
+                estimate = newstep;
+                return;
+            }
+            if( (double)(newstep)<(double)(estimate*0.01) )
+            {
+                estimate = estimate*0.01;
+                return;
+            }
+            if( (double)(newstep)>(double)(estimate*100) )
+            {
+                estimate = estimate*100;
+                return;
+            }
+            estimate = newstep;
+        }
+
+
+    }
+    public class minns
+    {
+        /*************************************************************************
+        This object stores temporaries for internal QP solver.
+        *************************************************************************/
+        public class minnsqp : apobject
+        {
+            public double fc;
+            public double fn;
+            public double[] xc;
+            public double[] xn;
+            public double[] x0;
+            public double[] gc;
+            public double[] d;
+            public double[,] uh;
+            public double[,] ch;
+            public double[,] rk;
+            public double[] invutc;
+            public double[] tmp0;
+            public int[] tmpidx;
+            public double[] tmpd;
+            public double[] tmpc;
+            public double[] tmplambdas;
+            public double[,] tmpc2;
+            public bool[] tmpb;
+            public snnls.snnlssolver nnls;
+            public minnsqp()
+            {
+                init();
+            }
+            public override void init()
+            {
+                xc = new double[0];
+                xn = new double[0];
+                x0 = new double[0];
+                gc = new double[0];
+                d = new double[0];
+                uh = new double[0,0];
+                ch = new double[0,0];
+                rk = new double[0,0];
+                invutc = new double[0];
+                tmp0 = new double[0];
+                tmpidx = new int[0];
+                tmpd = new double[0];
+                tmpc = new double[0];
+                tmplambdas = new double[0];
+                tmpc2 = new double[0,0];
+                tmpb = new bool[0];
+                nnls = new snnls.snnlssolver();
+            }
+            public override alglib.apobject make_copy()
+            {
+                minnsqp _result = new minnsqp();
+                _result.fc = fc;
+                _result.fn = fn;
+                _result.xc = (double[])xc.Clone();
+                _result.xn = (double[])xn.Clone();
+                _result.x0 = (double[])x0.Clone();
+                _result.gc = (double[])gc.Clone();
+                _result.d = (double[])d.Clone();
+                _result.uh = (double[,])uh.Clone();
+                _result.ch = (double[,])ch.Clone();
+                _result.rk = (double[,])rk.Clone();
+                _result.invutc = (double[])invutc.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmpidx = (int[])tmpidx.Clone();
+                _result.tmpd = (double[])tmpd.Clone();
+                _result.tmpc = (double[])tmpc.Clone();
+                _result.tmplambdas = (double[])tmplambdas.Clone();
+                _result.tmpc2 = (double[,])tmpc2.Clone();
+                _result.tmpb = (bool[])tmpb.Clone();
+                _result.nnls = (snnls.snnlssolver)nnls.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This object stores nonlinear optimizer state.
+        You should use functions provided by MinNS subpackage to work  with  this
+        object
+        *************************************************************************/
+        public class minnsstate : apobject
+        {
+            public int solvertype;
+            public int n;
+            public double epsx;
+            public int maxits;
+            public bool xrep;
+            public double diffstep;
+            public double[] s;
+            public double[] bndl;
+            public double[] bndu;
+            public bool[] hasbndl;
+            public bool[] hasbndu;
+            public int nec;
+            public int nic;
+            public double[,] cleic;
+            public int ng;
+            public int nh;
+            public double[] x;
+            public double f;
+            public double[] fi;
+            public double[,] j;
+            public bool needfij;
+            public bool needfi;
+            public bool xupdated;
+            public rcommstate rstate;
+            public rcommstate rstateags;
+            public hqrnd.hqrndstate agsrs;
+            public double agsradius;
+            public int agssamplesize;
+            public double agsraddecay;
+            public double agsalphadecay;
+            public double agsdecrease;
+            public double agsinitstp;
+            public double agsstattold;
+            public double agsshortstpabs;
+            public double agsshortstprel;
+            public double agsshortf;
+            public int agsshortlimit;
+            public double agsrhononlinear;
+            public int agsminupdate;
+            public int agsmaxraddecays;
+            public int agsmaxbacktrack;
+            public int agsmaxbacktracknonfull;
+            public double agspenaltylevel;
+            public double agspenaltyincrease;
+            public double[] xstart;
+            public double[] xc;
+            public double[] xn;
+            public double[] grs;
+            public double[] d;
+            public double[] colmax;
+            public double[] diagh;
+            public double[] signmin;
+            public double[] signmax;
+            public bool userterminationneeded;
+            public double[] scaledbndl;
+            public double[] scaledbndu;
+            public double[,] scaledcleic;
+            public double[] rholinear;
+            public double[,] samplex;
+            public double[,] samplegm;
+            public double[,] samplegmbc;
+            public double[] samplef;
+            public double[] samplef0;
+            public minnsqp nsqp;
+            public double[] tmp0;
+            public double[] tmp1;
+            public double[,] tmp2;
+            public int[] tmp3;
+            public double[] xbase;
+            public double[] fp;
+            public double[] fm;
+            public int repinneriterationscount;
+            public int repouteriterationscount;
+            public int repnfev;
+            public int repvaridx;
+            public int repfuncidx;
+            public int repterminationtype;
+            public double replcerr;
+            public double repnlcerr;
+            public int dbgncholesky;
+            public minnsstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                s = new double[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                hasbndl = new bool[0];
+                hasbndu = new bool[0];
+                cleic = new double[0,0];
+                x = new double[0];
+                fi = new double[0];
+                j = new double[0,0];
+                rstate = new rcommstate();
+                rstateags = new rcommstate();
+                agsrs = new hqrnd.hqrndstate();
+                xstart = new double[0];
+                xc = new double[0];
+                xn = new double[0];
+                grs = new double[0];
+                d = new double[0];
+                colmax = new double[0];
+                diagh = new double[0];
+                signmin = new double[0];
+                signmax = new double[0];
+                scaledbndl = new double[0];
+                scaledbndu = new double[0];
+                scaledcleic = new double[0,0];
+                rholinear = new double[0];
+                samplex = new double[0,0];
+                samplegm = new double[0,0];
+                samplegmbc = new double[0,0];
+                samplef = new double[0];
+                samplef0 = new double[0];
+                nsqp = new minnsqp();
+                tmp0 = new double[0];
+                tmp1 = new double[0];
+                tmp2 = new double[0,0];
+                tmp3 = new int[0];
+                xbase = new double[0];
+                fp = new double[0];
+                fm = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                minnsstate _result = new minnsstate();
+                _result.solvertype = solvertype;
+                _result.n = n;
+                _result.epsx = epsx;
+                _result.maxits = maxits;
+                _result.xrep = xrep;
+                _result.diffstep = diffstep;
+                _result.s = (double[])s.Clone();
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.hasbndl = (bool[])hasbndl.Clone();
+                _result.hasbndu = (bool[])hasbndu.Clone();
+                _result.nec = nec;
+                _result.nic = nic;
+                _result.cleic = (double[,])cleic.Clone();
+                _result.ng = ng;
+                _result.nh = nh;
+                _result.x = (double[])x.Clone();
+                _result.f = f;
+                _result.fi = (double[])fi.Clone();
+                _result.j = (double[,])j.Clone();
+                _result.needfij = needfij;
+                _result.needfi = needfi;
+                _result.xupdated = xupdated;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.rstateags = (rcommstate)rstateags.make_copy();
+                _result.agsrs = (hqrnd.hqrndstate)agsrs.make_copy();
+                _result.agsradius = agsradius;
+                _result.agssamplesize = agssamplesize;
+                _result.agsraddecay = agsraddecay;
+                _result.agsalphadecay = agsalphadecay;
+                _result.agsdecrease = agsdecrease;
+                _result.agsinitstp = agsinitstp;
+                _result.agsstattold = agsstattold;
+                _result.agsshortstpabs = agsshortstpabs;
+                _result.agsshortstprel = agsshortstprel;
+                _result.agsshortf = agsshortf;
+                _result.agsshortlimit = agsshortlimit;
+                _result.agsrhononlinear = agsrhononlinear;
+                _result.agsminupdate = agsminupdate;
+                _result.agsmaxraddecays = agsmaxraddecays;
+                _result.agsmaxbacktrack = agsmaxbacktrack;
+                _result.agsmaxbacktracknonfull = agsmaxbacktracknonfull;
+                _result.agspenaltylevel = agspenaltylevel;
+                _result.agspenaltyincrease = agspenaltyincrease;
+                _result.xstart = (double[])xstart.Clone();
+                _result.xc = (double[])xc.Clone();
+                _result.xn = (double[])xn.Clone();
+                _result.grs = (double[])grs.Clone();
+                _result.d = (double[])d.Clone();
+                _result.colmax = (double[])colmax.Clone();
+                _result.diagh = (double[])diagh.Clone();
+                _result.signmin = (double[])signmin.Clone();
+                _result.signmax = (double[])signmax.Clone();
+                _result.userterminationneeded = userterminationneeded;
+                _result.scaledbndl = (double[])scaledbndl.Clone();
+                _result.scaledbndu = (double[])scaledbndu.Clone();
+                _result.scaledcleic = (double[,])scaledcleic.Clone();
+                _result.rholinear = (double[])rholinear.Clone();
+                _result.samplex = (double[,])samplex.Clone();
+                _result.samplegm = (double[,])samplegm.Clone();
+                _result.samplegmbc = (double[,])samplegmbc.Clone();
+                _result.samplef = (double[])samplef.Clone();
+                _result.samplef0 = (double[])samplef0.Clone();
+                _result.nsqp = (minnsqp)nsqp.make_copy();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmp1 = (double[])tmp1.Clone();
+                _result.tmp2 = (double[,])tmp2.Clone();
+                _result.tmp3 = (int[])tmp3.Clone();
+                _result.xbase = (double[])xbase.Clone();
+                _result.fp = (double[])fp.Clone();
+                _result.fm = (double[])fm.Clone();
+                _result.repinneriterationscount = repinneriterationscount;
+                _result.repouteriterationscount = repouteriterationscount;
+                _result.repnfev = repnfev;
+                _result.repvaridx = repvaridx;
+                _result.repfuncidx = repfuncidx;
+                _result.repterminationtype = repterminationtype;
+                _result.replcerr = replcerr;
+                _result.repnlcerr = repnlcerr;
+                _result.dbgncholesky = dbgncholesky;
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure stores optimization report:
+        * IterationsCount           total number of inner iterations
+        * NFEV                      number of gradient evaluations
+        * TerminationType           termination type (see below)
+        * CErr                      maximum violation of all types of constraints
+        * LCErr                     maximum violation of linear constraints
+        * NLCErr                    maximum violation of nonlinear constraints
+
+        TERMINATION CODES
+
+        TerminationType field contains completion code, which can be:
+          -8    internal integrity control detected  infinite  or  NAN  values  in
+                function/gradient. Abnormal termination signalled.
+          -3    box constraints are inconsistent
+          -1    inconsistent parameters were passed:
+                * penalty parameter for minnssetalgoags() is zero,
+                  but we have nonlinear constraints set by minnssetnlc()
+           2    sampling radius decreased below epsx
+           5    MaxIts steps was taken
+           7    stopping conditions are too stringent,
+                further improvement is impossible,
+                X contains best point found so far.
+           8    User requested termination via MinNSRequestTermination()
+
+        Other fields of this structure are not documented and should not be used!
+        *************************************************************************/
+        public class minnsreport : apobject
+        {
+            public int iterationscount;
+            public int nfev;
+            public double cerr;
+            public double lcerr;
+            public double nlcerr;
+            public int terminationtype;
+            public int varidx;
+            public int funcidx;
+            public minnsreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                minnsreport _result = new minnsreport();
+                _result.iterationscount = iterationscount;
+                _result.nfev = nfev;
+                _result.cerr = cerr;
+                _result.lcerr = lcerr;
+                _result.nlcerr = nlcerr;
+                _result.terminationtype = terminationtype;
+                _result.varidx = varidx;
+                _result.funcidx = funcidx;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+                          NONSMOOTH NONCONVEX OPTIMIZATION
+                    SUBJECT TO BOX/LINEAR/NONLINEAR-NONSMOOTH CONSTRAINTS
+
+        DESCRIPTION:
+
+        The  subroutine  minimizes  function   F(x)  of N arguments subject to any
+        combination of:
+        * bound constraints
+        * linear inequality constraints
+        * linear equality constraints
+        * nonlinear equality constraints Gi(x)=0
+        * nonlinear inequality constraints Hi(x)<=0
+
+        IMPORTANT: see MinNSSetAlgoAGS for important  information  on  performance
+                   restrictions of AGS solver.
+
+        REQUIREMENTS:
+        * starting point X0 must be feasible or not too far away from the feasible
+          set
+        * F(), G(), H() are continuous, locally Lipschitz  and  continuously  (but
+          not necessarily twice) differentiable in an open dense  subset  of  R^N.
+          Functions F(), G() and H() may be nonsmooth and non-convex.
+          Informally speaking, it means  that  functions  are  composed  of  large
+          differentiable "patches" with nonsmoothness having  place  only  at  the
+          boundaries between these "patches".
+          Most real-life nonsmooth  functions  satisfy  these  requirements.  Say,
+          anything which involves finite number of abs(), min() and max() is  very
+          likely to pass the test.
+          Say, it is possible to optimize anything of the following:
+          * f=abs(x0)+2*abs(x1)
+          * f=max(x0,x1)
+          * f=sin(max(x0,x1)+abs(x2))
+        * for nonlinearly constrained problems: F()  must  be  bounded from  below
+          without nonlinear constraints (this requirement is due to the fact that,
+          contrary to box and linear constraints, nonlinear ones  require  special
+          handling).
+        * user must provide function value and gradient for F(), H(), G()  at  all
+          points where function/gradient can be calculated. If optimizer  requires
+          value exactly at the boundary between "patches" (say, at x=0 for f=abs(x)),
+          where gradient is not defined, user may resolve tie arbitrarily (in  our
+          case - return +1 or -1 at its discretion).
+        * NS solver supports numerical differentiation, i.e. it may  differentiate
+          your function for you,  but  it  results  in  2N  increase  of  function
+          evaluations. Not recommended unless you solve really small problems. See
+          minnscreatef() for more information on this functionality.
+
+        USAGE:
+
+        1. User initializes algorithm state with MinNSCreate() call  and   chooses
+           what NLC solver to use. There is some solver which is used by  default,
+           with default settings, but you should NOT rely on  default  choice.  It
+           may change in future releases of ALGLIB without notice, and no one  can
+           guarantee that new solver will be  able  to  solve  your  problem  with
+           default settings.
+
+           From the other side, if you choose solver explicitly, you can be pretty
+           sure that it will work with new ALGLIB releases.
+
+           In the current release following solvers can be used:
+           * AGS solver (activated with MinNSSetAlgoAGS() function)
+
+        2. User adds boundary and/or linear and/or nonlinear constraints by  means
+           of calling one of the following functions:
+           a) MinNSSetBC() for boundary constraints
+           b) MinNSSetLC() for linear constraints
+           c) MinNSSetNLC() for nonlinear constraints
+           You may combine (a), (b) and (c) in one optimization problem.
+
+        3. User sets scale of the variables with MinNSSetScale() function. It   is
+           VERY important to set  scale  of  the  variables,  because  nonlinearly
+           constrained problems are hard to solve when variables are badly scaled.
+
+        4. User sets stopping conditions with MinNSSetCond().
+
+        5. Finally, user calls MinNSOptimize()  function  which  takes   algorithm
+           state and pointer (delegate, etc) to callback function which calculates
+           F/G/H.
+
+        7. User calls MinNSResults() to get solution
+
+        8. Optionally user may call MinNSRestartFrom() to solve   another  problem
+           with same N but another starting point. MinNSRestartFrom()  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[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
+
+        NOTE: minnscreatef() function may be used if  you  do  not  have  analytic
+              gradient.   This   function  creates  solver  which  uses  numerical
+              differentiation with user-specified step.
+
+          -- ALGLIB --
+             Copyright 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnscreate(int n,
+            double[] x,
+            minnsstate state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(n>=1, "MinNSCreate: N<1");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinNSCreate: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "MinNSCreate: X contains infinite or NaN values");
+            minnsinitinternal(n, x, 0.0, state, _params);
+        }
+
+
+        /*************************************************************************
+        Version of minnscreatef() which uses numerical differentiation. I.e.,  you
+        do not have to calculate derivatives yourself. However, this version needs
+        2N times more function evaluations.
+
+        2-point differentiation formula is  used,  because  more  precise  4-point
+        formula is unstable when used on non-smooth functions.
+
+        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[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.
+            DiffStep-   differentiation  step,  DiffStep>0.   Algorithm   performs
+                        numerical differentiation  with  step  for  I-th  variable
+                        being equal to DiffStep*S[I] (here S[] is a  scale vector,
+                        set by minnssetscale() function).
+                        Do not use  too  small  steps,  because  it  may  lead  to
+                        catastrophic cancellation during intermediate calculations.
+
+        OUTPUT PARAMETERS:
+            State   -   structure stores algorithm state
+
+          -- ALGLIB --
+             Copyright 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnscreatef(int n,
+            double[] x,
+            double diffstep,
+            minnsstate state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(n>=1, "MinNSCreateF: N<1");
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinNSCreateF: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "MinNSCreateF: X contains infinite or NaN values");
+            alglib.ap.assert(math.isfinite(diffstep), "MinNSCreateF: DiffStep is infinite or NaN!");
+            alglib.ap.assert((double)(diffstep)>(double)(0), "MinNSCreateF: DiffStep is non-positive!");
+            minnsinitinternal(n, x, diffstep, state, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets boundary constraints.
+
+        Boundary constraints are inactive by default (after initial creation).
+        They are preserved after algorithm restart with minnsrestartfrom().
+
+        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: AGS 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 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnssetbc(minnsstate state,
+            double[] bndl,
+            double[] bndu,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = state.n;
+            alglib.ap.assert(alglib.ap.len(bndl)>=n, "MinNSSetBC: Length(BndL)<N");
+            alglib.ap.assert(alglib.ap.len(bndu)>=n, "MinNSSetBC: Length(BndU)<N");
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(bndl[i]) || Double.IsNegativeInfinity(bndl[i]), "MinNSSetBC: BndL contains NAN or +INF");
+                alglib.ap.assert(math.isfinite(bndu[i]) || Double.IsPositiveInfinity(bndu[i]), "MinNSSetBC: BndL contains NAN or -INF");
+                state.bndl[i] = bndl[i];
+                state.hasbndl[i] = math.isfinite(bndl[i]);
+                state.bndu[i] = bndu[i];
+                state.hasbndu[i] = math.isfinite(bndu[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets linear constraints.
+
+        Linear constraints are inactive by default (after initial creation).
+        They are preserved after algorithm restart with minnsrestartfrom().
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with minnscreate() 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: linear (non-bound) constraints are satisfied only approximately:
+
+        * there always exists some minor violation (about current sampling  radius
+          in magnitude during optimization, about EpsX in the solution) due to use
+          of penalty method to handle constraints.
+        * 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 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnssetlc(minnsstate state,
+            double[,] c,
+            int[] ct,
+            int k,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int i_ = 0;
+
+            n = state.n;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(k>=0, "MinNSSetLC: K<0");
+            alglib.ap.assert(alglib.ap.cols(c)>=n+1 || k==0, "MinNSSetLC: Cols(C)<N+1");
+            alglib.ap.assert(alglib.ap.rows(c)>=k, "MinNSSetLC: Rows(C)<K");
+            alglib.ap.assert(alglib.ap.len(ct)>=k, "MinNSSetLC: Length(CT)<K");
+            alglib.ap.assert(apserv.apservisfinitematrix(c, k, n+1, _params), "MinNSSetLC: C contains infinite or NaN values!");
+            
+            //
+            // Handle zero K
+            //
+            if( k==0 )
+            {
+                state.nec = 0;
+                state.nic = 0;
+                return;
+            }
+            
+            //
+            // Equality constraints are stored first, in the upper
+            // NEC rows of State.CLEIC matrix. Inequality constraints
+            // are stored in the next NIC rows.
+            //
+            // NOTE: we convert inequality constraints to the form
+            // A*x<=b before copying them.
+            //
+            apserv.rmatrixsetlengthatleast(ref state.cleic, k, n+1, _params);
+            state.nec = 0;
+            state.nic = 0;
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]==0 )
+                {
+                    for(i_=0; i_<=n;i_++)
+                    {
+                        state.cleic[state.nec,i_] = c[i,i_];
+                    }
+                    state.nec = state.nec+1;
+                }
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]!=0 )
+                {
+                    if( ct[i]>0 )
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec+state.nic,i_] = -c[i,i_];
+                        }
+                    }
+                    else
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec+state.nic,i_] = c[i,i_];
+                        }
+                    }
+                    state.nic = state.nic+1;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function sets nonlinear constraints.
+
+        In fact, this function sets NUMBER of nonlinear  constraints.  Constraints
+        itself (constraint functions) are passed to minnsoptimize() method.   This
+        method requires user-defined vector function F[]  and  its  Jacobian  J[],
+        where:
+        * first component of F[] and first row  of  Jacobian  J[]  correspond   to
+          function being minimized
+        * next NLEC components of F[] (and rows  of  J)  correspond  to  nonlinear
+          equality constraints G_i(x)=0
+        * next NLIC components of F[] (and rows  of  J)  correspond  to  nonlinear
+          inequality constraints H_i(x)<=0
+
+        NOTE: you may combine nonlinear constraints with linear/boundary ones.  If
+              your problem has mixed constraints, you  may explicitly specify some
+              of them as linear ones. It may help optimizer to  handle  them  more
+              efficiently.
+
+        INPUT PARAMETERS:
+            State   -   structure previously allocated with minnscreate() call.
+            NLEC    -   number of Non-Linear Equality Constraints (NLEC), >=0
+            NLIC    -   number of Non-Linear Inquality Constraints (NLIC), >=0
+
+        NOTE 1: nonlinear constraints are satisfied only  approximately!   It   is
+                possible   that  algorithm  will  evaluate  function  outside   of
+                the feasible area!
+
+        NOTE 2: algorithm scales variables  according  to   scale   specified   by
+                minnssetscale()  function,  so  it can handle problems with  badly
+                scaled variables (as long as we KNOW their scales).
+
+                However,  there  is  no  way  to  automatically  scale   nonlinear
+                constraints Gi(x) and Hi(x). Inappropriate scaling  of  Gi/Hi  may
+                ruin convergence. Solving problem with  constraint  "1000*G0(x)=0"
+                is NOT same as solving it with constraint "0.001*G0(x)=0".
+
+                It  means  that  YOU  are  the  one who is responsible for correct
+                scaling of nonlinear constraints Gi(x) and Hi(x). We recommend you
+                to scale nonlinear constraints in such way that I-th component  of
+                dG/dX (or dH/dx) has approximately unit  magnitude  (for  problems
+                with unit scale)  or  has  magnitude approximately equal to 1/S[i]
+                (where S is a scale set by minnssetscale() function).
+
+        NOTE 3: nonlinear constraints are always hard to handle,  no  matter  what
+                algorithm you try to use. Even basic box/linear constraints modify
+                function  curvature   by  adding   valleys  and  ridges.  However,
+                nonlinear constraints add valleys which are very  hard  to  follow
+                due to their "curved" nature.
+
+                It means that optimization with single nonlinear constraint may be
+                significantly slower than optimization with multiple linear  ones.
+                It is normal situation, and we recommend you to  carefully  choose
+                Rho parameter of minnssetalgoags(), because too  large  value  may
+                slow down convergence.
+
+
+          -- ALGLIB --
+             Copyright 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnssetnlc(minnsstate state,
+            int nlec,
+            int nlic,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(nlec>=0, "MinNSSetNLC: NLEC<0");
+            alglib.ap.assert(nlic>=0, "MinNSSetNLC: NLIC<0");
+            state.ng = nlec;
+            state.nh = nlic;
+            state.fi = new double[1+state.ng+state.nh];
+            state.j = new double[1+state.ng+state.nh, state.n];
+        }
+
+
+        /*************************************************************************
+        This function sets stopping conditions for iterations of optimizer.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            EpsX    -   >=0
+                        The AGS solver finishes its work if  on  k+1-th  iteration
+                        sampling radius decreases below EpsX.
+            MaxIts  -   maximum number of iterations. If MaxIts=0, the  number  of
+                        iterations is unlimited.
+
+        Passing EpsX=0  and  MaxIts=0  (simultaneously)  will  lead  to  automatic
+        stopping criterion selection. We do not recommend you to rely  on  default
+        choice in production code.
+
+          -- ALGLIB --
+             Copyright 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnssetcond(minnsstate state,
+            double epsx,
+            int maxits,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(epsx), "MinNSSetCond: EpsX is not finite number");
+            alglib.ap.assert((double)(epsx)>=(double)(0), "MinNSSetCond: negative EpsX");
+            alglib.ap.assert(maxits>=0, "MinNSSetCond: negative MaxIts!");
+            if( (double)(epsx)==(double)(0) && maxits==0 )
+            {
+                epsx = 1.0E-6;
+            }
+            state.epsx = epsx;
+            state.maxits = maxits;
+        }
+
+
+        /*************************************************************************
+        This function sets scaling coefficients for NLC 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].
+
+        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 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnssetscale(minnsstate state,
+            double[] s,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(alglib.ap.len(s)>=state.n, "MinNSSetScale: Length(S)<N");
+            for(i=0; i<=state.n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(s[i]), "MinNSSetScale: S contains infinite or NAN elements");
+                alglib.ap.assert((double)(s[i])!=(double)(0), "MinNSSetScale: S contains zero elements");
+                state.s[i] = Math.Abs(s[i]);
+            }
+        }
+
+
+        /*************************************************************************
+        This function tells MinNS unit to use  AGS  (adaptive  gradient  sampling)
+        algorithm for nonsmooth constrained  optimization.  This  algorithm  is  a
+        slight modification of one described in  "An  Adaptive  Gradient  Sampling
+        Algorithm for Nonsmooth Optimization" by Frank E. Curtisy and Xiaocun Quez.
+
+        This optimizer has following benefits and drawbacks:
+        + robustness; it can be used with nonsmooth and nonconvex functions.
+        + relatively easy tuning; most of the metaparameters are easy to select.
+        - it has convergence of steepest descent, slower than CG/LBFGS.
+        - each iteration involves evaluation of ~2N gradient values  and  solution
+          of 2Nx2N quadratic programming problem, which  limits  applicability  of
+          algorithm by small-scale problems (up to 50-100).
+
+        IMPORTANT: this  algorithm  has  convergence  guarantees,   i.e.  it  will
+                   steadily move towards some stationary point of the function.
+
+                   However, "stationary point" does not  always  mean  "solution".
+                   Nonsmooth problems often have "flat spots",  i.e.  areas  where
+                   function do not change at all. Such "flat spots" are stationary
+                   points by definition, and algorithm may be caught here.
+
+                   Nonsmooth CONVEX tasks are not prone to  this  problem. Say, if
+                   your function has form f()=MAX(f0,f1,...), and f_i are  convex,
+                   then f() is convex too and you have guaranteed  convergence  to
+                   solution.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            Radius  -   initial sampling radius, >=0.
+
+                        Internally multiplied  by  vector of  per-variable  scales
+                        specified by minnssetscale()).
+
+                        You should select relatively large sampling radius, roughly
+                        proportional to scaled length of the first  steps  of  the
+                        algorithm. Something close to 0.1 in magnitude  should  be
+                        good for most problems.
+
+                        AGS solver can automatically decrease radius, so too large
+                        radius is  not a problem (assuming that you  won't  choose
+                        so large radius that algorithm  will  sample  function  in
+                        too far away points, where gradient value is irrelevant).
+
+                        Too small radius won't cause algorithm to fail, but it may
+                        slow down algorithm (it may  have  to  perform  too  short
+                        steps).
+            Penalty -   penalty coefficient for nonlinear constraints:
+                        * for problem with nonlinear constraints  should  be  some
+                          problem-specific  positive   value,  large  enough  that
+                          penalty term changes shape of the function.
+                          Starting  from  some  problem-specific   value   penalty
+                          coefficient becomes  large  enough  to  exactly  enforce
+                          nonlinear constraints;  larger  values  do  not  improve
+                          precision.
+                          Increasing it too much may slow down convergence, so you
+                          should choose it carefully.
+                        * can be zero for problems WITHOUT  nonlinear  constraints
+                          (i.e. for unconstrained ones or ones with  just  box  or
+                          linear constraints)
+                        * if you specify zero value for problem with at least  one
+                          nonlinear  constraint,  algorithm  will  terminate  with
+                          error code -1.
+
+        ALGORITHM OUTLINE
+
+        The very basic outline of unconstrained AGS algorithm is given below:
+
+        0. If sampling radius is below EpsX  or  we  performed  more  then  MaxIts
+           iterations - STOP.
+        1. sample O(N) gradient values at random locations  around  current point;
+           informally speaking, this sample is an implicit piecewise  linear model
+           of the function, although algorithm formulation does  not  mention that
+           explicitly
+        2. solve quadratic programming problem in order to find descent direction
+        3. if QP solver tells us that we  are  near  solution,  decrease  sampling
+           radius and move to (0)
+        4. perform backtracking line search
+        5. after moving to new point, goto (0)
+
+        As for the constraints:
+        * box constraints are handled exactly  by  modification  of  the  function
+          being minimized
+        * linear/nonlinear constraints are handled by adding L1  penalty.  Because
+          our solver can handle nonsmoothness, we can  use  L1  penalty  function,
+          which is an exact one  (i.e.  exact  solution  is  returned  under  such
+          penalty).
+        * penalty coefficient for  linear  constraints  is  chosen  automatically;
+          however, penalty coefficient for nonlinear constraints must be specified
+          by user.
+
+          -- ALGLIB --
+             Copyright 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnssetalgoags(minnsstate state,
+            double radius,
+            double penalty,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(radius), "MinNSSetAlgoAGS: Radius is not finite");
+            alglib.ap.assert((double)(radius)>(double)(0), "MinNSSetAlgoAGS: Radius<=0");
+            alglib.ap.assert(math.isfinite(penalty), "MinNSSetAlgoAGS: Penalty is not finite");
+            alglib.ap.assert((double)(penalty)>=(double)(0.0), "MinNSSetAlgoAGS: Penalty<0");
+            state.agsrhononlinear = penalty;
+            state.agsradius = radius;
+            state.solvertype = 0;
+        }
+
+
+        /*************************************************************************
+        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 minnsoptimize().
+
+          -- ALGLIB --
+             Copyright 28.11.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnssetxrep(minnsstate state,
+            bool needxrep,
+            alglib.xparams _params)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        This subroutine submits request for termination of running  optimizer.  It
+        should be called from user-supplied callback when user decides that it  is
+        time to "smoothly" terminate optimization process.  As  result,  optimizer
+        stops at point which was "current accepted" when termination  request  was
+        submitted and returns error code 8 (successful termination).
+
+        INPUT PARAMETERS:
+            State   -   optimizer structure
+
+        NOTE: after  request  for  termination  optimizer  may   perform   several
+              additional calls to user-supplied callbacks. It does  NOT  guarantee
+              to stop immediately - it just guarantees that these additional calls
+              will be discarded later.
+
+        NOTE: calling this function on optimizer which is NOT running will have no
+              effect.
+
+        NOTE: multiple calls to this function are possible. First call is counted,
+              subsequent calls are silently ignored.
+
+          -- ALGLIB --
+             Copyright 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnsrequesttermination(minnsstate state,
+            alglib.xparams _params)
+        {
+            state.userterminationneeded = true;
+        }
+
+
+        /*************************************************************************
+
+        NOTES:
+
+        1. This function has two different implementations: one which  uses  exact
+           (analytical) user-supplied Jacobian, and one which uses  only  function
+           vector and numerically  differentiates  function  in  order  to  obtain
+           gradient.
+
+           Depending  on  the  specific  function  used to create optimizer object
+           you should choose appropriate variant of  minnsoptimize() -  one  which
+           accepts function AND Jacobian or one which accepts ONLY function.
+
+           Be careful to choose variant of minnsoptimize()  which  corresponds  to
+           your optimization scheme! Table below lists different  combinations  of
+           callback (function/gradient) passed to minnsoptimize()    and  specific
+           function used to create optimizer.
+
+
+                             |         USER PASSED TO minnsoptimize()
+           CREATED WITH      |  function only   |  function and gradient
+           ------------------------------------------------------------
+           minnscreatef()    |     works               FAILS
+           minnscreate()     |     FAILS               works
+
+           Here "FAILS" denotes inappropriate combinations  of  optimizer creation
+           function  and  minnsoptimize()  version.   Attemps   to    use     such
+           combination will lead to exception. Either  you  did  not pass gradient
+           when it WAS needed or you passed gradient when it was NOT needed.
+
+          -- ALGLIB --
+             Copyright 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static bool minnsiteration(minnsstate state,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+            int k = 0;
+            int n = 0;
+            int ng = 0;
+            int nh = 0;
+            double v = 0;
+            double xp = 0;
+            double xm = 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];
+                k = state.rstate.ia[1];
+                n = state.rstate.ia[2];
+                ng = state.rstate.ia[3];
+                nh = state.rstate.ia[4];
+                v = state.rstate.ra[0];
+                xp = state.rstate.ra[1];
+                xm = state.rstate.ra[2];
+            }
+            else
+            {
+                i = 359;
+                k = -58;
+                n = -919;
+                ng = -909;
+                nh = 81;
+                v = 255;
+                xp = 74;
+                xm = -788;
+            }
+            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
+            //
+            
+            //
+            // Init
+            //
+            state.replcerr = 0.0;
+            state.repnlcerr = 0.0;
+            state.repterminationtype = 0;
+            state.repinneriterationscount = 0;
+            state.repouteriterationscount = 0;
+            state.repnfev = 0;
+            state.repvaridx = 0;
+            state.repfuncidx = 0;
+            state.userterminationneeded = false;
+            state.dbgncholesky = 0;
+            n = state.n;
+            ng = state.ng;
+            nh = state.nh;
+            clearrequestfields(state, _params);
+            
+            //
+            // AGS solver
+            //
+            if( state.solvertype!=0 )
+            {
+                goto lbl_4;
+            }
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                apserv.rvectorsetlengthatleast(ref state.xbase, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.fm, 1+ng+nh, _params);
+                apserv.rvectorsetlengthatleast(ref state.fp, 1+ng+nh, _params);
+            }
+            state.rstateags.ia = new int[13+1];
+            state.rstateags.ba = new bool[3+1];
+            state.rstateags.ra = new double[9+1];
+            state.rstateags.stage = -1;
+        lbl_6:
+            if( !agsiteration(state, _params) )
+            {
+                goto lbl_7;
+            }
+            
+            //
+            // Numerical differentiation (if needed) - intercept NeedFiJ
+            // request and replace it by sequence of NeedFi requests
+            //
+            if( !((double)(state.diffstep)!=(double)(0) && state.needfij) )
+            {
+                goto lbl_8;
+            }
+            state.needfij = false;
+            state.needfi = true;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xbase[i_] = state.x[i_];
+            }
+            k = 0;
+        lbl_10:
+            if( k>n-1 )
+            {
+                goto lbl_12;
+            }
+            v = state.xbase[k];
+            xm = v-state.diffstep*state.s[k];
+            xp = v+state.diffstep*state.s[k];
+            if( state.hasbndl[k] && (double)(xm)<(double)(state.bndl[k]) )
+            {
+                xm = state.bndl[k];
+            }
+            if( state.hasbndu[k] && (double)(xp)>(double)(state.bndu[k]) )
+            {
+                xp = state.bndu[k];
+            }
+            alglib.ap.assert((double)(xm)<=(double)(xp), "MinNS: integrity check failed");
+            if( (double)(xm)==(double)(xp) )
+            {
+                goto lbl_13;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.x[k] = xm;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.fm[i_] = state.fi[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.x[k] = xp;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.fp[i_] = state.fi[i_];
+            }
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.j[i_,k] = state.fp[i_];
+            }
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.j[i_,k] = state.j[i_,k] - state.fm[i_];
+            }
+            v = 1/(xp-xm);
+            for(i_=0; i_<=ng+nh;i_++)
+            {
+                state.j[i_,k] = v*state.j[i_,k];
+            }
+            state.repnfev = state.repnfev+2;
+            goto lbl_14;
+        lbl_13:
+            for(i=0; i<=ng+nh; i++)
+            {
+                state.j[i,k] = 0.0;
+            }
+        lbl_14:
+            k = k+1;
+            goto lbl_10;
+        lbl_12:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            
+            //
+            // Restore previous values of fields and continue
+            //
+            state.needfi = false;
+            state.needfij = true;
+            goto lbl_6;
+        lbl_8:
+            
+            //
+            // Forward request to caller
+            //
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            apserv.inc(ref state.repnfev, _params);
+            goto lbl_6;
+        lbl_7:
+            result = false;
+            return result;
+        lbl_4:
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = i;
+            state.rstate.ia[1] = k;
+            state.rstate.ia[2] = n;
+            state.rstate.ia[3] = ng;
+            state.rstate.ia[4] = nh;
+            state.rstate.ra[0] = v;
+            state.rstate.ra[1] = xp;
+            state.rstate.ra[2] = xm;
+            return result;
+        }
+
+
+        /*************************************************************************
+        MinNS 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:
+                        * -8   internal integrity control  detected  infinite  or
+                               NAN   values   in   function/gradient.    Abnormal
+                               termination signalled.
+                        * -3   box constraints are inconsistent
+                        * -1   inconsistent parameters were passed:
+                               * penalty parameter for minnssetalgoags() is zero,
+                                 but we have nonlinear constraints set by minnssetnlc()
+                        *  2   sampling radius decreased below epsx
+                        *  7    stopping conditions are too stringent,
+                                further improvement is impossible,
+                                X contains best point found so far.
+                        *  8    User requested termination via minnsrequesttermination()
+
+          -- ALGLIB --
+             Copyright 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnsresults(minnsstate state,
+            ref double[] x,
+            minnsreport rep,
+            alglib.xparams _params)
+        {
+            x = new double[0];
+
+            minnsresultsbuf(state, ref x, rep, _params);
+        }
+
+
+        /*************************************************************************
+
+        Buffered implementation of minnsresults() 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 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnsresultsbuf(minnsstate state,
+            ref double[] x,
+            minnsreport rep,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            if( alglib.ap.len(x)<state.n )
+            {
+                x = new double[state.n];
+            }
+            rep.iterationscount = state.repinneriterationscount;
+            rep.nfev = state.repnfev;
+            rep.varidx = state.repvaridx;
+            rep.funcidx = state.repfuncidx;
+            rep.terminationtype = state.repterminationtype;
+            rep.cerr = Math.Max(state.replcerr, state.repnlcerr);
+            rep.lcerr = state.replcerr;
+            rep.nlcerr = state.repnlcerr;
+            if( state.repterminationtype>0 )
+            {
+                for(i_=0; i_<=state.n-1;i_++)
+                {
+                    x[i_] = state.xc[i_];
+                }
+            }
+            else
+            {
+                for(i=0; i<=state.n-1; i++)
+                {
+                    x[i] = Double.NaN;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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 minnscreate() call.
+            X       -   new starting point.
+
+          -- ALGLIB --
+             Copyright 18.05.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minnsrestartfrom(minnsstate state,
+            double[] x,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i_ = 0;
+
+            n = state.n;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(alglib.ap.len(x)>=n, "MinNSRestartFrom: Length(X)<N");
+            alglib.ap.assert(apserv.isfinitevector(x, n, _params), "MinNSRestartFrom: 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.ra = new double[2+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state, _params);
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forget to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(minnsstate state,
+            alglib.xparams _params)
+        {
+            state.needfi = false;
+            state.needfij = false;
+            state.xupdated = false;
+        }
+
+
+        /*************************************************************************
+        Internal initialization subroutine.
+        Sets default NLC solver with default criteria.
+        *************************************************************************/
+        private static void minnsinitinternal(int n,
+            double[] x,
+            double diffstep,
+            minnsstate state,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double[,] c = new double[0,0];
+            int[] ct = new int[0];
+
+            state.agsinitstp = 0.2;
+            state.agsstattold = 1.0E-10;
+            state.agsshortstpabs = 1.0E-10;
+            state.agsshortstprel = 0.75;
+            state.agsshortf = 10*math.machineepsilon;
+            state.agsrhononlinear = 0.0;
+            state.agsraddecay = 0.2;
+            state.agsalphadecay = 0.5;
+            state.agsdecrease = 0.1;
+            state.agsmaxraddecays = 50;
+            state.agsmaxbacktrack = 20;
+            state.agsmaxbacktracknonfull = 8;
+            state.agspenaltylevel = 50.0;
+            state.agspenaltyincrease = 100.0;
+            state.agsminupdate = Math.Max(5, n/2);
+            state.agssamplesize = Math.Max(2*n+1, state.agsminupdate+1);
+            state.agsshortlimit = 4+state.agssamplesize/state.agsminupdate;
+            
+            //
+            // Initialize other params
+            //
+            state.n = n;
+            state.diffstep = diffstep;
+            state.bndl = new double[n];
+            state.hasbndl = new bool[n];
+            state.bndu = new double[n];
+            state.hasbndu = new bool[n];
+            state.s = new double[n];
+            state.xstart = new double[n];
+            state.xc = new double[n];
+            state.xn = new double[n];
+            state.d = new double[n];
+            state.x = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                state.bndl[i] = Double.NegativeInfinity;
+                state.hasbndl[i] = false;
+                state.bndu[i] = Double.PositiveInfinity;
+                state.hasbndu[i] = false;
+                state.s[i] = 1.0;
+                state.xstart[i] = x[i];
+                state.xc[i] = x[i];
+            }
+            minnssetlc(state, c, ct, 0, _params);
+            minnssetnlc(state, 0, 0, _params);
+            minnssetcond(state, 0.0, 0, _params);
+            minnssetxrep(state, false, _params);
+            minnssetalgoags(state, 0.1, 1000.0, _params);
+            minnsrestartfrom(state, x, _params);
+        }
+
+
+        /*************************************************************************
+        This function performs actual processing for AUL algorith. It expects that
+        caller redirects its reverse communication  requests  NeedFiJ/XUpdated  to
+        external user who will provide analytic derivative (or handle reports about
+        progress).
+
+        In case external user does not have analytic derivative, it is responsibility
+        of caller to intercept NeedFiJ request and  replace  it  with  appropriate
+        numerical differentiation scheme.
+
+          -- ALGLIB --
+             Copyright 06.06.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static bool agsiteration(minnsstate state,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int n = 0;
+            int nec = 0;
+            int nic = 0;
+            int ng = 0;
+            int nh = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double radius0 = 0;
+            double radius = 0;
+            int radiusdecays = 0;
+            double alpha = 0;
+            double recommendedstep = 0;
+            double dnrm = 0;
+            double dg = 0;
+            double v = 0;
+            double vv = 0;
+            int maxsamplesize = 0;
+            int cursamplesize = 0;
+            double v0 = 0;
+            double v1 = 0;
+            bool restartneeded = new bool();
+            bool b = new bool();
+            bool alphadecreased = new bool();
+            int shortstepscnt = 0;
+            int backtrackits = 0;
+            int maxbacktrackits = 0;
+            bool fullsample = 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.rstateags.stage>=0 )
+            {
+                n = state.rstateags.ia[0];
+                nec = state.rstateags.ia[1];
+                nic = state.rstateags.ia[2];
+                ng = state.rstateags.ia[3];
+                nh = state.rstateags.ia[4];
+                i = state.rstateags.ia[5];
+                j = state.rstateags.ia[6];
+                k = state.rstateags.ia[7];
+                radiusdecays = state.rstateags.ia[8];
+                maxsamplesize = state.rstateags.ia[9];
+                cursamplesize = state.rstateags.ia[10];
+                shortstepscnt = state.rstateags.ia[11];
+                backtrackits = state.rstateags.ia[12];
+                maxbacktrackits = state.rstateags.ia[13];
+                restartneeded = state.rstateags.ba[0];
+                b = state.rstateags.ba[1];
+                alphadecreased = state.rstateags.ba[2];
+                fullsample = state.rstateags.ba[3];
+                radius0 = state.rstateags.ra[0];
+                radius = state.rstateags.ra[1];
+                alpha = state.rstateags.ra[2];
+                recommendedstep = state.rstateags.ra[3];
+                dnrm = state.rstateags.ra[4];
+                dg = state.rstateags.ra[5];
+                v = state.rstateags.ra[6];
+                vv = state.rstateags.ra[7];
+                v0 = state.rstateags.ra[8];
+                v1 = state.rstateags.ra[9];
+            }
+            else
+            {
+                n = 809;
+                nec = 205;
+                nic = -838;
+                ng = 939;
+                nh = -526;
+                i = 763;
+                j = -541;
+                k = -698;
+                radiusdecays = -900;
+                maxsamplesize = -318;
+                cursamplesize = -940;
+                shortstepscnt = 1016;
+                backtrackits = -229;
+                maxbacktrackits = -536;
+                restartneeded = true;
+                b = true;
+                alphadecreased = false;
+                fullsample = false;
+                radius0 = -722;
+                radius = -413;
+                alpha = -461;
+                recommendedstep = 927;
+                dnrm = 201;
+                dg = 922;
+                v = -154;
+                vv = 306;
+                v0 = -1011;
+                v1 = 951;
+            }
+            if( state.rstateags.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstateags.stage==1 )
+            {
+                goto lbl_1;
+            }
+            if( state.rstateags.stage==2 )
+            {
+                goto lbl_2;
+            }
+            if( state.rstateags.stage==3 )
+            {
+                goto lbl_3;
+            }
+            
+            //
+            // Routine body
+            //
+            alglib.ap.assert(state.solvertype==0, "MinNS: internal error");
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            ng = state.ng;
+            nh = state.nh;
+            
+            //
+            // Check consistency of parameters
+            //
+            if( ng+nh>0 && (double)(state.agsrhononlinear)==(double)(0) )
+            {
+                state.repterminationtype = -1;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Allocate arrays.
+            //
+            apserv.rvectorsetlengthatleast(ref state.colmax, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.diagh, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.signmin, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.signmax, n, _params);
+            maxsamplesize = state.agssamplesize;
+            apserv.rmatrixsetlengthatleast(ref state.samplex, maxsamplesize+1, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.samplegm, maxsamplesize+1, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.samplegmbc, maxsamplesize+1, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.samplef, maxsamplesize+1, _params);
+            apserv.rvectorsetlengthatleast(ref state.samplef0, maxsamplesize+1, _params);
+            apserv.rvectorsetlengthatleast(ref state.grs, n, _params);
+            
+            //
+            // Prepare optimizer
+            //
+            apserv.rvectorsetlengthatleast(ref state.tmp0, maxsamplesize, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmp1, maxsamplesize, _params);
+            apserv.ivectorsetlengthatleast(ref state.tmp3, 1, _params);
+            apserv.rmatrixsetlengthatleast(ref state.tmp2, 1, maxsamplesize+1, _params);
+            for(i=0; i<=maxsamplesize-1; i++)
+            {
+                state.tmp0[i] = 0.0;
+                state.tmp1[i] = Double.PositiveInfinity;
+            }
+            
+            //
+            // Prepare RNG, seed it with fixed values so
+            // that each run on same problem yeilds same results
+            //
+            hqrnd.hqrndseed(7235, 98532, state.agsrs, _params);
+            
+            //
+            // Prepare initial point subject to current bound constraints and
+            // perform scaling of bound constraints, linear constraints, point itself
+            //
+            apserv.rvectorsetlengthatleast(ref state.scaledbndl, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.scaledbndu, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                
+                //
+                // Check and scale constraints
+                //
+                if( (state.hasbndl[i] && state.hasbndu[i]) && (double)(state.bndu[i])<(double)(state.bndl[i]) )
+                {
+                    state.repterminationtype = -3;
+                    result = false;
+                    return result;
+                }
+                if( state.hasbndl[i] )
+                {
+                    state.scaledbndl[i] = state.bndl[i]/state.s[i];
+                }
+                else
+                {
+                    state.scaledbndl[i] = Double.NegativeInfinity;
+                }
+                if( state.hasbndu[i] )
+                {
+                    state.scaledbndu[i] = state.bndu[i]/state.s[i];
+                }
+                else
+                {
+                    state.scaledbndu[i] = Double.PositiveInfinity;
+                }
+                if( state.hasbndl[i] && state.hasbndu[i] )
+                {
+                    alglib.ap.assert((double)(state.scaledbndl[i])<=(double)(state.scaledbndu[i]), "MinNS: integrity check failed");
+                }
+                if( (state.hasbndl[i] && state.hasbndu[i]) && (double)(state.bndl[i])==(double)(state.bndu[i]) )
+                {
+                    alglib.ap.assert((double)(state.scaledbndl[i])==(double)(state.scaledbndu[i]), "MinNS: integrity check failed");
+                }
+                
+                //
+                // Scale and constrain point
+                //
+                state.xc[i] = state.xstart[i];
+                if( state.hasbndl[i] && (double)(state.xc[i])<=(double)(state.bndl[i]) )
+                {
+                    state.xc[i] = state.scaledbndl[i];
+                    continue;
+                }
+                if( state.hasbndu[i] && (double)(state.xc[i])>=(double)(state.bndu[i]) )
+                {
+                    state.xc[i] = state.scaledbndu[i];
+                    continue;
+                }
+                state.xc[i] = state.xc[i]/state.s[i];
+                if( state.hasbndl[i] && (double)(state.xc[i])<=(double)(state.scaledbndl[i]) )
+                {
+                    state.xc[i] = state.scaledbndl[i];
+                }
+                if( state.hasbndu[i] && (double)(state.xc[i])>=(double)(state.scaledbndu[i]) )
+                {
+                    state.xc[i] = state.scaledbndu[i];
+                }
+            }
+            apserv.rmatrixsetlengthatleast(ref state.scaledcleic, nec+nic, n+1, _params);
+            apserv.rvectorsetlengthatleast(ref state.rholinear, nec+nic, _params);
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                
+                //
+                // Initial value of penalty coefficient is zero
+                //
+                state.rholinear[i] = 0.0;
+                
+                //
+                // Scale and normalize linear constraints
+                //
+                vv = 0.0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = state.cleic[i,j]*state.s[j];
+                    state.scaledcleic[i,j] = v;
+                    vv = vv+v*v;
+                }
+                vv = Math.Sqrt(vv);
+                state.scaledcleic[i,n] = state.cleic[i,n];
+                if( (double)(vv)>(double)(0) )
+                {
+                    for(j=0; j<=n; j++)
+                    {
+                        state.scaledcleic[i,j] = state.scaledcleic[i,j]/vv;
+                    }
+                }
+            }
+            
+            //
+            // Main cycle
+            //
+            // We maintain several variables during iteration:
+            // * RecommendedStep-   current estimate of recommended step length;
+            //                      must be Radius0 on first entry
+            // * Radius         -   current sampling radius
+            // * CurSampleSize  -   current sample size (may change in future versions)
+            // * FullSample     -   whether we have full sample, or only partial one
+            // * RadiusDecays   -   total number of decreases performed for sampling radius
+            //
+            radius = state.agsradius;
+            radius0 = radius;
+            recommendedstep = Math.Min(radius0, state.agsinitstp);
+            cursamplesize = 1;
+            radiusdecays = 0;
+            shortstepscnt = 0;
+            fullsample = false;
+        lbl_4:
+            if( false )
+            {
+                goto lbl_5;
+            }
+            
+            //
+            // First phase of iteration - central point:
+            //
+            // 1. evaluate function at central point - first entry in sample.
+            //    Its status is ignored, it is always recalculated.
+            // 2. report point and check gradient/function value for NAN/INF
+            // 3. check penalty coefficients for linear terms; increase them
+            //    if directional derivative of function being optimized (not
+            //    merit function!) is larger than derivative of penalty.
+            // 4. update report on constraint violation
+            //
+            cursamplesize = Math.Max(cursamplesize, 1);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.samplex[0,i_] = state.xc[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xc[i_];
+            }
+            unscalepointbc(state, state.x, _params);
+            clearrequestfields(state, _params);
+            state.needfij = true;
+            state.rstateags.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfij = false;
+            state.replcerr = 0.0;
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                v = -state.scaledcleic[i,n];
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+state.scaledcleic[i,j]*state.xc[j];
+                }
+                if( i>=nec && (double)(v)<=(double)(0) )
+                {
+                    continue;
+                }
+                state.replcerr = Math.Max(state.replcerr, Math.Abs(v));
+            }
+            state.repnlcerr = 0.0;
+            for(i=1; i<=ng+nh; i++)
+            {
+                v = state.fi[i];
+                if( i>ng && (double)(v)<=(double)(0) )
+                {
+                    continue;
+                }
+                state.repnlcerr = Math.Max(state.repnlcerr, Math.Abs(v));
+            }
+            for(j=0; j<=n-1; j++)
+            {
+                state.grs[j] = state.j[0,j]*state.s[j];
+            }
+            generatemeritfunction(state, 0, _params);
+            if( !state.xrep )
+            {
+                goto lbl_6;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xc[i_];
+            }
+            state.f = state.samplef0[0];
+            unscalepointbc(state, state.x, _params);
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstateags.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.xupdated = false;
+        lbl_6:
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                state.repterminationtype = 8;
+                goto lbl_5;
+            }
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.samplegm[0,i]);
+            }
+            if( !math.isfinite(v) || !math.isfinite(state.samplef[0]) )
+            {
+                
+                //
+                // Abnormal termination - infinities in function/gradient
+                //
+                state.repterminationtype = -8;
+                goto lbl_5;
+            }
+            restartneeded = false;
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                
+                //
+                // Evaluate penalty function.
+                //
+                // Skip update if penalty is satisfied exactly (this check
+                // also covers situations when I-th row is exactly zero).
+                //
+                v = -state.scaledcleic[i,n];
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+state.scaledcleic[i,j]*state.xc[j];
+                }
+                if( i<nec && (double)(v)==(double)(0) )
+                {
+                    continue;
+                }
+                if( i>=nec && (double)(v)<=(double)(0) )
+                {
+                    continue;
+                }
+                
+                //
+                // Calculate directional derivative, compare it with threshold.
+                //
+                // NOTE: we rely on the fact that ScaledCLEIC is normalized
+                //
+                alglib.ap.assert((double)(state.agspenaltylevel)>(double)(1.0), "MinNS: integrity error");
+                alglib.ap.assert((double)(state.agspenaltyincrease)>(double)(state.agspenaltylevel), "MinNS: integrity error");
+                v = 0.0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+state.grs[j]*state.scaledcleic[i,j];
+                }
+                v = Math.Abs(v);
+                if( (double)(v*state.agspenaltylevel)>(double)(state.rholinear[i]) )
+                {
+                    state.rholinear[i] = v*state.agspenaltyincrease;
+                    restartneeded = true;
+                }
+            }
+            if( restartneeded )
+            {
+                cursamplesize = 0;
+                goto lbl_4;
+            }
+            
+            //
+            // Check stopping conditions.
+            //
+            if( radiusdecays>=state.agsmaxraddecays )
+            {
+                
+                //
+                // Too many attempts to decrease radius
+                //
+                state.repterminationtype = 7;
+                goto lbl_5;
+            }
+            if( state.repinneriterationscount>=state.maxits && state.maxits>0 )
+            {
+                
+                //
+                // Too many iterations
+                //
+                state.repterminationtype = 5;
+                goto lbl_5;
+            }
+            if( (double)(radius)<=(double)(state.epsx*state.agsraddecay) )
+            {
+                
+                //
+                // Radius is smaller than required step tolerance multiplied by radius decay.
+                //
+                // Additional decay is required in order to make sure that optimization session
+                // with radius equal to EpsX was successfully done.
+                //
+                state.repterminationtype = 2;
+                goto lbl_5;
+            }
+            
+            //
+            // Update sample:
+            //
+            // 1. invalidate entries which are too far away from XC
+            //    and move all valid entries to beginning of the sample.
+            // 2. add new entries until we have AGSSampleSize
+            //    items in our sample. We remove oldest entries from
+            //    sample until we have enough place to add at least
+            //    AGSMinUpdate items.
+            // 3. prepare "modified" gradient sample with respect to
+            //    boundary constraints.
+            //
+            alglib.ap.assert(cursamplesize>=1, "MinNS: integrity check failed");
+            k = 1;
+            for(i=1; i<=cursamplesize-1; i++)
+            {
+                
+                //
+                // If entry is outside of Radius-ball around XC, discard it.
+                //
+                v = 0.0;
+                for(j=0; j<=n-1; j++)
+                {
+                    v = Math.Max(v, Math.Abs(state.samplex[i,j]-state.xc[j]));
+                }
+                if( (double)(v)>(double)(radius) )
+                {
+                    continue;
+                }
+                
+                //
+                // If central point is exactly at boundary, and corresponding
+                // component of entry is OUT of boundary, entry is discarded.
+                //
+                b = false;
+                for(j=0; j<=n-1; j++)
+                {
+                    b = b || ((state.hasbndl[j] && (double)(state.xc[j])==(double)(state.scaledbndl[j])) && (double)(state.samplex[i,j])!=(double)(state.scaledbndl[j]));
+                    b = b || ((state.hasbndu[j] && (double)(state.xc[j])==(double)(state.scaledbndu[j])) && (double)(state.samplex[i,j])!=(double)(state.scaledbndu[j]));
+                }
+                if( b )
+                {
+                    continue;
+                }
+                
+                //
+                // Move to the beginning
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.samplex[k,i_] = state.samplex[i,i_];
+                }
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.samplegm[k,i_] = state.samplegm[i,i_];
+                }
+                state.samplef[k] = state.samplef[i];
+                state.samplef0[k] = state.samplef0[i];
+                k = k+1;
+            }
+            cursamplesize = k;
+            if( state.agssamplesize-cursamplesize<state.agsminupdate )
+            {
+                
+                //
+                // Remove oldest entries
+                //
+                k = state.agsminupdate-(state.agssamplesize-cursamplesize);
+                alglib.ap.assert(k<=cursamplesize-1, "MinNS: integrity check failed");
+                for(i=1; i<=cursamplesize-k-1; i++)
+                {
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.samplex[i,i_] = state.samplex[i+k,i_];
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.samplegm[i,i_] = state.samplegm[i+k,i_];
+                    }
+                    state.samplef[i] = state.samplef[i+k];
+                    state.samplef0[i] = state.samplef0[i+k];
+                }
+                cursamplesize = cursamplesize-k;
+            }
+            k = 0;
+            i = cursamplesize;
+        lbl_8:
+            if( i>Math.Min(cursamplesize+state.agsminupdate, state.agssamplesize)-1 )
+            {
+                goto lbl_10;
+            }
+            for(j=0; j<=n-1; j++)
+            {
+                
+                //
+                // Undistorted position
+                //
+                state.samplex[i,j] = state.xc[j];
+                
+                //
+                // Do not apply distortion, if we are exactly at boundary constraint.
+                //
+                if( (state.hasbndl[j] && state.hasbndu[j]) && (double)(state.scaledbndl[j])==(double)(state.scaledbndu[j]) )
+                {
+                    continue;
+                }
+                if( state.hasbndl[j] && (double)(state.samplex[i,j])==(double)(state.scaledbndl[j]) )
+                {
+                    continue;
+                }
+                if( state.hasbndu[j] && (double)(state.samplex[i,j])==(double)(state.scaledbndu[j]) )
+                {
+                    continue;
+                }
+                
+                //
+                // Apply distortion
+                //
+                if( (double)(hqrnd.hqrnduniformr(state.agsrs, _params))>=(double)(0.5) )
+                {
+                    
+                    //
+                    // Sample at the left side with 50% probability
+                    //
+                    v0 = state.samplex[i,j]-radius;
+                    v1 = state.samplex[i,j];
+                    if( state.hasbndl[j] )
+                    {
+                        v0 = Math.Max(state.scaledbndl[j], v0);
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Sample at the right side with 50% probability
+                    //
+                    v0 = state.samplex[i,j];
+                    v1 = state.samplex[i,j]+radius;
+                    if( state.hasbndu[j] )
+                    {
+                        v1 = Math.Min(state.scaledbndu[j], v1);
+                    }
+                }
+                alglib.ap.assert((double)(v1)>=(double)(v0), "MinNS: integrity check failed");
+                state.samplex[i,j] = apserv.boundval(v0+(v1-v0)*hqrnd.hqrnduniformr(state.agsrs, _params), v0, v1, _params);
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.samplex[i,i_];
+            }
+            unscalepointbc(state, state.x, _params);
+            clearrequestfields(state, _params);
+            state.needfij = true;
+            state.rstateags.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.needfij = false;
+            generatemeritfunction(state, i, _params);
+            k = k+1;
+            i = i+1;
+            goto lbl_8;
+        lbl_10:
+            cursamplesize = cursamplesize+k;
+            fullsample = cursamplesize==state.agssamplesize;
+            for(j=0; j<=cursamplesize-1; j++)
+            {
+                
+                //
+                // For J-th element in gradient sample, process all of its components
+                // and modify them according to status of box constraints
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    alglib.ap.assert(!state.hasbndl[i] || (double)(state.xc[i])>=(double)(state.scaledbndl[i]), "MinNS: integrity error");
+                    alglib.ap.assert(!state.hasbndu[i] || (double)(state.xc[i])<=(double)(state.scaledbndu[i]), "MinNS: integrity error");
+                    state.samplegmbc[j,i] = state.samplegm[j,i];
+                    if( (state.hasbndl[i] && state.hasbndu[i]) && (double)(state.scaledbndl[i])==(double)(state.scaledbndu[i]) )
+                    {
+                        
+                        //
+                        // I-th box constraint is of equality type (lower bound matches upper one).
+                        // Simplest case, always active.
+                        //
+                        state.samplegmbc[j,i] = 0.0;
+                        continue;
+                    }
+                    if( state.hasbndl[i] && (double)(state.xc[i])==(double)(state.scaledbndl[i]) )
+                    {
+                        
+                        //
+                        // We are at lower bound.
+                        //
+                        // A bit more complex:
+                        // * first, we have to activate/deactivate constraint depending on gradient at XC
+                        // * second, in any case, I-th column of gradient sample must be non-positive
+                        //
+                        if( (double)(state.samplegm[0,i])>=(double)(0.0) )
+                        {
+                            state.samplegmbc[j,i] = 0.0;
+                        }
+                        state.samplegmbc[j,i] = Math.Min(state.samplegmbc[j,i], 0.0);
+                        continue;
+                    }
+                    if( state.hasbndu[i] && (double)(state.xc[i])==(double)(state.scaledbndu[i]) )
+                    {
+                        
+                        //
+                        // We are at upper bound.
+                        //
+                        // A bit more complex:
+                        // * first, we have to activate/deactivate constraint depending on gradient at XC
+                        // * second, in any case, I-th column of gradient sample must be non-negative
+                        //
+                        if( (double)(state.samplegm[0,i])<=(double)(0.0) )
+                        {
+                            state.samplegmbc[j,i] = 0.0;
+                        }
+                        state.samplegmbc[j,i] = Math.Max(state.samplegmbc[j,i], 0.0);
+                        continue;
+                    }
+                }
+            }
+            
+            //
+            // Calculate diagonal Hessian.
+            //
+            // This Hessian serves two purposes:
+            // * first, it improves performance of gradient descent step
+            // * second, it improves condition number of QP subproblem
+            //   solved to determine step
+            //
+            // The idea is that for each variable we check whether sample
+            // includes entries with alternating sign of gradient:
+            // * if gradients with different signs are present, Hessian
+            //   component is set to M/R, where M is a maximum magnitude
+            //   of corresponding gradient component, R is a sampling radius.
+            //   Note that sign=0 and sign=1 are treated as different ones
+            // * if all gradients have same sign, Hessian component is
+            //   set to M/R0, where R0 is initial sampling radius.
+            //
+            for(j=0; j<=n-1; j++)
+            {
+                state.colmax[j] = 0.0;
+                state.signmin[j] = 1;
+                state.signmax[j] = -1;
+            }
+            for(i=0; i<=cursamplesize-1; i++)
+            {
+                for(j=0; j<=n-1; j++)
+                {
+                    v = state.samplegmbc[i,j];
+                    state.colmax[j] = Math.Max(state.colmax[j], Math.Abs(v));
+                    state.signmin[j] = Math.Min(state.signmin[j], Math.Sign(v));
+                    state.signmax[j] = Math.Max(state.signmax[j], Math.Sign(v));
+                }
+            }
+            for(j=0; j<=n-1; j++)
+            {
+                if( (double)(state.signmin[j])!=(double)(state.signmax[j]) )
+                {
+                    
+                    //
+                    // Alternating signs of gradient - step is proportional to current sampling radius
+                    //
+                    alglib.ap.assert((double)(state.colmax[j])!=(double)(0), "MinNS: integrity check failed");
+                    alglib.ap.assert((double)(radius)!=(double)(0), "MinNS: integrity check failed");
+                    state.diagh[j] = state.colmax[j]/radius;
+                    continue;
+                }
+                if( (double)(state.colmax[j])!=(double)(0) )
+                {
+                    
+                    //
+                    // Non-alternating sign of gradient, but non-zero.
+                    // Step is proportional to initial sampling radius
+                    //
+                    alglib.ap.assert((double)(radius0)!=(double)(0), "MinNS: integrity check failed");
+                    state.diagh[j] = state.colmax[j]/radius0;
+                    continue;
+                }
+                state.diagh[j] = 1;
+            }
+            
+            //
+            // PROJECTION PHASE
+            //
+            // We project zero vector on convex hull of gradient sample.
+            // If projection is small enough, we decrease radius and restart.
+            // Otherwise, this phase returns search direction in State.D.
+            //
+            // NOTE: because we use iterative solver, it may have trouble
+            //       dealing with ill-conditioned problems. So we also employ
+            //       second, backup test for stationarity - when too many
+            //       subsequent backtracking searches resulted in short steps.
+            //
+            solveqp(state.samplegmbc, state.diagh, cursamplesize, n, ref state.tmp0, ref state.dbgncholesky, state.nsqp, _params);
+            for(j=0; j<=n-1; j++)
+            {
+                state.d[j] = 0.0;
+            }
+            for(i=0; i<=cursamplesize-1; i++)
+            {
+                v = state.tmp0[i];
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.d[i_] = state.d[i_] + v*state.samplegmbc[i,i_];
+                }
+            }
+            v = 0.0;
+            for(j=0; j<=n-1; j++)
+            {
+                v = Math.Max(v, Math.Abs(state.d[j]/apserv.coalesce(state.colmax[j], 1.0, _params)));
+            }
+            if( (double)(v)<=(double)(state.agsstattold) )
+            {
+                
+                //
+                // Stationarity test succeded.
+                // Decrease radius and restart.
+                //
+                // NOTE: we also clear ShortStepsCnt on restart
+                //
+                radius = radius*state.agsraddecay;
+                shortstepscnt = 0;
+                apserv.inc(ref radiusdecays, _params);
+                apserv.inc(ref state.repinneriterationscount, _params);
+                goto lbl_4;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.d[i] = -(state.d[i]/state.diagh[i]);
+            }
+            
+            //
+            // Perform backtracking line search.
+            // Update initial step length depending on search results.
+            // Here we assume that D is non-zero.
+            //
+            // NOTE: if AGSShortLimit subsequent line searches resulted
+            //       in steps shorter than AGSStatTolStp, we decrease radius.
+            //
+            dnrm = 0.0;
+            dg = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                dnrm = dnrm+math.sqr(state.d[i]);
+                dg = dg+state.d[i]*state.samplegmbc[0,i];
+            }
+            dnrm = Math.Sqrt(dnrm);
+            alglib.ap.assert((double)(dnrm)>(double)(0), "MinNS: integrity error");
+            alpha = recommendedstep/dnrm;
+            alphadecreased = false;
+            backtrackits = 0;
+            if( fullsample )
+            {
+                maxbacktrackits = state.agsmaxbacktrack;
+            }
+            else
+            {
+                maxbacktrackits = state.agsmaxbacktracknonfull;
+            }
+        lbl_11:
+            if( false )
+            {
+                goto lbl_12;
+            }
+            
+            //
+            // Prepare XN and evaluate merit function at XN
+            //
+            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_] + alpha*state.d[i_];
+            }
+            optserv.enforceboundaryconstraints(state.xn, state.scaledbndl, state.hasbndl, state.scaledbndu, state.hasbndu, n, 0, _params);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.samplex[maxsamplesize,i_] = state.xn[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xn[i_];
+            }
+            unscalepointbc(state, state.x, _params);
+            clearrequestfields(state, _params);
+            state.needfij = true;
+            state.rstateags.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.needfij = false;
+            generatemeritfunction(state, maxsamplesize, _params);
+            
+            //
+            // Check sufficient decrease condition
+            //
+            alglib.ap.assert((double)(dnrm)>(double)(0), "MinNS: integrity error");
+            if( (double)(state.samplef[maxsamplesize])<=(double)(state.samplef[0]+alpha*state.agsdecrease*dg) )
+            {
+                goto lbl_12;
+            }
+            
+            //
+            // Decrease Alpha
+            //
+            alpha = alpha*state.agsalphadecay;
+            alphadecreased = true;
+            
+            //
+            // Update and check iterations counter.
+            //
+            apserv.inc(ref backtrackits, _params);
+            if( backtrackits>=maxbacktrackits )
+            {
+                
+                //
+                // Too many backtracking searches performed without success.
+                // Terminate iterations.
+                //
+                alpha = 0.0;
+                alphadecreased = true;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.xn[i_] = state.xc[i_];
+                }
+                goto lbl_12;
+            }
+            goto lbl_11;
+        lbl_12:
+            if( ((double)(alpha*dnrm)<=(double)(state.agsshortstpabs) || (double)(alpha*dnrm)<=(double)(state.agsshortstprel*radius)) || (double)(Math.Abs(state.samplef[0]-state.samplef[maxsamplesize]))<=(double)(state.agsshortf) )
+            {
+                apserv.inc(ref shortstepscnt, _params);
+            }
+            else
+            {
+                shortstepscnt = 0;
+            }
+            if( shortstepscnt>=state.agsshortlimit )
+            {
+                
+                //
+                // Too many subsequent short steps.
+                //
+                // It may be possible that optimizer is unable to find out
+                // that we have to decrease radius because of ill-conditioned
+                // gradients.
+                //
+                // Decrease radius and restart.
+                //
+                radius = radius*state.agsraddecay;
+                shortstepscnt = 0;
+                apserv.inc(ref radiusdecays, _params);
+                apserv.inc(ref state.repinneriterationscount, _params);
+                goto lbl_4;
+            }
+            if( !alphadecreased )
+            {
+                recommendedstep = recommendedstep*2.0;
+            }
+            if( alphadecreased && fullsample )
+            {
+                recommendedstep = recommendedstep*0.5;
+            }
+            
+            //
+            // Next iteration
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xc[i_] = state.xn[i_];
+            }
+            apserv.inc(ref state.repinneriterationscount, _params);
+            goto lbl_4;
+        lbl_5:
+            
+            //
+            // Convert back from scaled to unscaled representation
+            //
+            unscalepointbc(state, state.xc, _params);
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstateags.ia[0] = n;
+            state.rstateags.ia[1] = nec;
+            state.rstateags.ia[2] = nic;
+            state.rstateags.ia[3] = ng;
+            state.rstateags.ia[4] = nh;
+            state.rstateags.ia[5] = i;
+            state.rstateags.ia[6] = j;
+            state.rstateags.ia[7] = k;
+            state.rstateags.ia[8] = radiusdecays;
+            state.rstateags.ia[9] = maxsamplesize;
+            state.rstateags.ia[10] = cursamplesize;
+            state.rstateags.ia[11] = shortstepscnt;
+            state.rstateags.ia[12] = backtrackits;
+            state.rstateags.ia[13] = maxbacktrackits;
+            state.rstateags.ba[0] = restartneeded;
+            state.rstateags.ba[1] = b;
+            state.rstateags.ba[2] = alphadecreased;
+            state.rstateags.ba[3] = fullsample;
+            state.rstateags.ra[0] = radius0;
+            state.rstateags.ra[1] = radius;
+            state.rstateags.ra[2] = alpha;
+            state.rstateags.ra[3] = recommendedstep;
+            state.rstateags.ra[4] = dnrm;
+            state.rstateags.ra[5] = dg;
+            state.rstateags.ra[6] = v;
+            state.rstateags.ra[7] = vv;
+            state.rstateags.ra[8] = v0;
+            state.rstateags.ra[9] = v1;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function calculates merit function (target function +  penalties  for
+        violation of non-box constraints),  using  State.X  (unscaled),  State.Fi,
+        State.J (unscaled) and State.SampleX (scaled) as inputs.
+
+        Results are loaded:
+        * target function value - to State.SampleF0[SampleIdx]
+        * merit function value - to State.SampleF[SampleIdx]
+        * gradient of merit function - to State.SampleGM[SampleIdx]
+
+          -- ALGLIB --
+             Copyright 02.06.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static void generatemeritfunction(minnsstate state,
+            int sampleidx,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int nec = 0;
+            int nic = 0;
+            int ng = 0;
+            int nh = 0;
+            double v = 0;
+            double s = 0;
+
+            n = state.n;
+            nec = state.nec;
+            nic = state.nic;
+            ng = state.ng;
+            nh = state.nh;
+            
+            //
+            // Integrity check
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(!state.hasbndl[i] || (double)(state.x[i])>=(double)(state.bndl[i]), "MinNS: integrity error");
+                alglib.ap.assert(!state.hasbndu[i] || (double)(state.x[i])<=(double)(state.bndu[i]), "MinNS: integrity error");
+            }
+            
+            //
+            // Prepare "raw" function
+            //
+            state.samplef[sampleidx] = state.fi[0];
+            state.samplef0[sampleidx] = state.fi[0];
+            for(j=0; j<=n-1; j++)
+            {
+                state.samplegm[sampleidx,j] = state.j[0,j]*state.s[j];
+            }
+            
+            //
+            // Modify merit function with linear constraints
+            //
+            for(i=0; i<=nec+nic-1; i++)
+            {
+                v = -state.scaledcleic[i,n];
+                for(j=0; j<=n-1; j++)
+                {
+                    v = v+state.scaledcleic[i,j]*state.samplex[sampleidx,j];
+                }
+                if( i>=nec && (double)(v)<(double)(0) )
+                {
+                    continue;
+                }
+                state.samplef[sampleidx] = state.samplef[sampleidx]+state.rholinear[i]*Math.Abs(v);
+                s = Math.Sign(v);
+                for(j=0; j<=n-1; j++)
+                {
+                    state.samplegm[sampleidx,j] = state.samplegm[sampleidx,j]+state.rholinear[i]*s*state.scaledcleic[i,j];
+                }
+            }
+            
+            //
+            // Modify merit function with nonlinear constraints
+            //
+            for(i=1; i<=ng+nh; i++)
+            {
+                v = state.fi[i];
+                if( i<=ng && (double)(v)==(double)(0) )
+                {
+                    continue;
+                }
+                if( i>ng && (double)(v)<=(double)(0) )
+                {
+                    continue;
+                }
+                state.samplef[sampleidx] = state.samplef[sampleidx]+state.agsrhononlinear*Math.Abs(v);
+                s = Math.Sign(v);
+                for(j=0; j<=n-1; j++)
+                {
+                    state.samplegm[sampleidx,j] = state.samplegm[sampleidx,j]+state.agsrhononlinear*s*state.j[i,j]*state.s[j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function performs transformation of  X  from  scaled  coordinates  to
+        unscaled ones, paying special attention to box constraints:
+        * points which were exactly at the boundary before scaling will be  mapped
+          to corresponding boundary after scaling
+        * in any case, unscaled box constraints will be satisfied
+
+          -- ALGLIB --
+             Copyright 02.06.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static void unscalepointbc(minnsstate state,
+            double[] x,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            for(i=0; i<=state.n-1; i++)
+            {
+                if( state.hasbndl[i] && (double)(x[i])<=(double)(state.scaledbndl[i]) )
+                {
+                    x[i] = state.bndl[i];
+                    continue;
+                }
+                if( state.hasbndu[i] && (double)(x[i])>=(double)(state.scaledbndu[i]) )
+                {
+                    x[i] = state.bndu[i];
+                    continue;
+                }
+                x[i] = x[i]*state.s[i];
+                if( state.hasbndl[i] && (double)(x[i])<=(double)(state.bndl[i]) )
+                {
+                    x[i] = state.bndl[i];
+                }
+                if( state.hasbndu[i] && (double)(x[i])>=(double)(state.bndu[i]) )
+                {
+                    x[i] = state.bndu[i];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function solves QP problem of the form
+
+                [                        ]
+            min [ 0.5*c'*(G*inv(H)*G')*c ] s.t. c[i]>=0, SUM(c[i])=1.0
+                [                        ]
+
+        where G is stored in SampleG[] array, diagonal H is stored in DiagH[].
+
+        DbgNCholesky is incremented every time we perform Cholesky decomposition.
+
+          -- ALGLIB --
+             Copyright 02.06.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static void solveqp(double[,] sampleg,
+            double[] diagh,
+            int nsample,
+            int nvars,
+            ref double[] coeffs,
+            ref int dbgncholesky,
+            minnsqp state,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            double v = 0;
+            double vv = 0;
+            int n = 0;
+            int idx0 = 0;
+            int idx1 = 0;
+            int ncandbnd = 0;
+            int innerits = 0;
+            int outerits = 0;
+            double dnrm = 0;
+            double stp = 0;
+            double stpmax = 0;
+            int actidx = 0;
+            double dtol = 0;
+            bool kickneeded = new bool();
+            double kicklength = 0;
+            double lambdav = 0;
+            double maxdiag = 0;
+            bool wasactivation = new bool();
+            bool werechanges = new bool();
+            int termcnt = 0;
+            int i_ = 0;
+
+            n = nsample;
+            
+            //
+            // Allocate arrays, prepare data
+            //
+            apserv.rvectorsetlengthatleast(ref coeffs, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.xc, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.xn, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.x0, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.gc, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.d, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.uh, n, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.ch, n, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.rk, nsample, nvars, _params);
+            apserv.rvectorsetlengthatleast(ref state.invutc, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmp0, n, _params);
+            apserv.bvectorsetlengthatleast(ref state.tmpb, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.xc[i] = 1.0/n;
+                coeffs[i] = 1.0/n;
+            }
+            for(i=0; i<=nsample-1; i++)
+            {
+                for(j=0; j<=nvars-1; j++)
+                {
+                    state.rk[i,j] = sampleg[i,j]/Math.Sqrt(diagh[j]);
+                }
+            }
+            ablas.rmatrixsyrk(nsample, nvars, 1.0, state.rk, 0, 0, 0, 0.0, state.uh, 0, 0, true, _params);
+            maxdiag = 0.0;
+            for(i=0; i<=nsample-1; i++)
+            {
+                maxdiag = Math.Max(maxdiag, state.uh[i,i]);
+            }
+            maxdiag = apserv.coalesce(maxdiag, 1.0, _params);
+            
+            //
+            // Main cycle:
+            //
+            innerits = 0;
+            outerits = 0;
+            dtol = 1.0E5*math.machineepsilon;
+            kicklength = math.machineepsilon;
+            lambdav = 1.0E5*math.machineepsilon;
+            termcnt = 0;
+            while( true )
+            {
+                
+                //
+                // Save current point to X0
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.x0[i_] = state.xc[i_];
+                }
+                
+                //
+                // Calculate gradient at initial point, solve NNLS problem
+                // to determine descent direction D subject to constraints.
+                //
+                // In order to do so we solve following constrained
+                // minimization problem:
+                //         (                         )^2
+                //     min ( SUM(lambda[i]*A[i]) + G )
+                //         (                         )
+                // Here:
+                // * G is a gradient (column vector)
+                // * A[i] is a column vector of I-th constraint
+                // * lambda[i] is a Lagrange multiplier corresponding to I-th constraint
+                //
+                // NOTE: all A[i] except for last one have only one element being set,
+                //       so we rely on sparse capabilities of NNLS solver. However,
+                //       in order to use these capabilities we have to reorder variables
+                //       in such way that sparse ones come first.
+                //
+                // After finding lambda[] coefficients, we can find constrained descent
+                // direction by subtracting lambda[i]*A[i] from D=-G. We make use of the
+                // fact that first NCandBnd columns are just columns of identity matrix,
+                // so we can perform exact projection by explicitly setting elements of D
+                // to zeros.
+                //
+                qpcalculategradfunc(sampleg, diagh, nsample, nvars, state.xc, ref state.gc, ref state.fc, ref state.tmp0, _params);
+                apserv.ivectorsetlengthatleast(ref state.tmpidx, n, _params);
+                apserv.rvectorsetlengthatleast(ref state.tmpd, n, _params);
+                apserv.rmatrixsetlengthatleast(ref state.tmpc2, n, 1, _params);
+                idx0 = 0;
+                ncandbnd = 0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (double)(state.xc[i])==(double)(0.0) )
+                    {
+                        ncandbnd = ncandbnd+1;
+                    }
+                }
+                idx1 = ncandbnd;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (double)(state.xc[i])==(double)(0.0) )
+                    {
+                        
+                        //
+                        // Candidate for activation of boundary constraint,
+                        // comes first.
+                        //
+                        // NOTE: multiplication by -1 is due to the fact that
+                        //       it is lower bound, and has specific direction
+                        //       of constraint gradient.
+                        //
+                        state.tmpidx[idx0] = i;
+                        state.tmpd[idx0] = -state.gc[i]*-1;
+                        state.tmpc2[idx0,0] = 1.0*-1;
+                        idx0 = idx0+1;
+                    }
+                    else
+                    {
+                        
+                        //
+                        // We are far away from boundary.
+                        //
+                        state.tmpidx[idx1] = i;
+                        state.tmpd[idx1] = -state.gc[i];
+                        state.tmpc2[idx1,0] = 1.0;
+                        idx1 = idx1+1;
+                    }
+                }
+                alglib.ap.assert(idx0==ncandbnd, "MinNSQP: integrity check failed");
+                alglib.ap.assert(idx1==n, "MinNSQP: integrity check failed");
+                snnls.snnlsinit(n, 1, n, state.nnls, _params);
+                snnls.snnlssetproblem(state.nnls, state.tmpc2, state.tmpd, ncandbnd, 1, n, _params);
+                snnls.snnlsdropnnc(state.nnls, ncandbnd, _params);
+                snnls.snnlssolve(state.nnls, ref state.tmplambdas, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    state.d[i] = -state.gc[i]-state.tmplambdas[ncandbnd];
+                }
+                for(i=0; i<=ncandbnd-1; i++)
+                {
+                    if( (double)(state.tmplambdas[i])>(double)(0) )
+                    {
+                        state.d[state.tmpidx[i]] = 0.0;
+                    }
+                }
+                
+                //
+                // Additional stage to "polish" D (improve situation
+                // with sum-to-one constraint and boundary constraints)
+                // and to perform additional integrity check.
+                //
+                // After this stage we are pretty sure that:
+                // * if x[i]=0.0, then d[i]>=0.0
+                // * if d[i]<0.0, then x[i]>0.0
+                //
+                v = 0.0;
+                vv = 0.0;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (double)(state.xc[i])==(double)(0.0) && (double)(state.d[i])<(double)(0.0) )
+                    {
+                        state.d[i] = 0.0;
+                    }
+                    v = v+state.d[i];
+                    vv = Math.Max(vv, Math.Abs(state.gc[i]));
+                }
+                alglib.ap.assert((double)(Math.Abs(v))<(double)(1.0E5*Math.Sqrt(n)*math.machineepsilon*Math.Max(vv, 1.0)), "MinNSQP: integrity check failed");
+                
+                //
+                // Decide whether we need "kick" stage: special stage
+                // that moves us away from boundary constraints which are
+                // not strictly active (i.e. such constraints that x[i]=0.0 and d[i]>0).
+                //
+                // If we need kick stage, we make a kick - and restart iteration.
+                // If not, after this block we can rely on the fact that
+                // for all x[i]=0.0 we have d[i]=0.0
+                //
+                kickneeded = false;
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (double)(state.xc[i])==(double)(0.0) && (double)(state.d[i])>(double)(0.0) )
+                    {
+                        kickneeded = true;
+                    }
+                }
+                if( kickneeded )
+                {
+                    
+                    //
+                    // Perform kick.
+                    // Restart.
+                    // Do not increase outer iterations counter.
+                    //
+                    v = 0.0;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        if( (double)(state.xc[i])==(double)(0.0) && (double)(state.d[i])>(double)(0.0) )
+                        {
+                            state.xc[i] = state.xc[i]+kicklength;
+                        }
+                        v = v+state.xc[i];
+                    }
+                    alglib.ap.assert((double)(v)>(double)(0.0), "MinNSQP: integrity check failed");
+                    for(i=0; i<=n-1; i++)
+                    {
+                        state.xc[i] = state.xc[i]/v;
+                    }
+                    apserv.inc(ref innerits, _params);
+                    continue;
+                }
+                
+                //
+                // Calculate Cholesky decomposition of constrained Hessian
+                // for Newton phase.
+                //
+                while( true )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        
+                        //
+                        // Diagonal element
+                        //
+                        if( (double)(state.xc[i])>(double)(0.0) )
+                        {
+                            state.ch[i,i] = state.uh[i,i]+lambdav*maxdiag;
+                        }
+                        else
+                        {
+                            state.ch[i,i] = 1.0;
+                        }
+                        
+                        //
+                        // Offdiagonal elements
+                        //
+                        for(j=i+1; j<=n-1; j++)
+                        {
+                            if( (double)(state.xc[i])>(double)(0.0) && (double)(state.xc[j])>(double)(0.0) )
+                            {
+                                state.ch[i,j] = state.uh[i,j];
+                            }
+                            else
+                            {
+                                state.ch[i,j] = 0.0;
+                            }
+                        }
+                    }
+                    apserv.inc(ref dbgncholesky, _params);
+                    if( !trfac.spdmatrixcholeskyrec(ref state.ch, 0, n, true, ref state.tmp0, _params) )
+                    {
+                        
+                        //
+                        // Cholesky decomposition failed.
+                        // Increase LambdaV and repeat iteration.
+                        // Do not increase outer iterations counter.
+                        //
+                        lambdav = lambdav*10;
+                        continue;
+                    }
+                    break;
+                }
+                
+                //
+                // Newton phase
+                //
+                while( true )
+                {
+                    
+                    //
+                    // Calculate constrained (equality and sum-to-one) descent direction D.
+                    //
+                    // Here we use Sherman-Morrison update to calculate direction subject to
+                    // sum-to-one constraint.
+                    //
+                    qpcalculategradfunc(sampleg, diagh, nsample, nvars, state.xc, ref state.gc, ref state.fc, ref state.tmp0, _params);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        if( (double)(state.xc[i])>(double)(0.0) )
+                        {
+                            state.invutc[i] = 1.0;
+                            state.d[i] = -state.gc[i];
+                        }
+                        else
+                        {
+                            state.invutc[i] = 0.0;
+                            state.d[i] = 0.0;
+                        }
+                    }
+                    qpsolveut(state.ch, n, state.invutc, _params);
+                    qpsolveut(state.ch, n, state.d, _params);
+                    v = 0.0;
+                    vv = 0.0;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        vv = vv+math.sqr(state.invutc[i]);
+                        v = v+state.invutc[i]*state.d[i];
+                    }
+                    for(i=0; i<=n-1; i++)
+                    {
+                        state.d[i] = state.d[i]-v/vv*state.invutc[i];
+                    }
+                    qpsolveu(state.ch, n, state.d, _params);
+                    v = 0.0;
+                    k = 0;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        v = v+state.d[i];
+                        if( (double)(state.d[i])!=(double)(0.0) )
+                        {
+                            k = k+1;
+                        }
+                    }
+                    if( k>0 && (double)(v)>(double)(0.0) )
+                    {
+                        vv = v/k;
+                        for(i=0; i<=n-1; i++)
+                        {
+                            if( (double)(state.d[i])!=(double)(0.0) )
+                            {
+                                state.d[i] = state.d[i]-vv;
+                            }
+                        }
+                    }
+                    
+                    //
+                    // Calculate length of D, maximum step and component which is
+                    // activated by this step.
+                    //
+                    // Break if D is exactly zero. We do not break here if DNrm is
+                    // small - this check is performed later. It is important to
+                    // perform last step with nearly-zero D, it allows us to have
+                    // extra-precision in solution which is often needed for convergence
+                    // of AGS algorithm.
+                    //
+                    dnrm = 0.0;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        dnrm = dnrm+math.sqr(state.d[i]);
+                    }
+                    dnrm = Math.Sqrt(dnrm);
+                    actidx = -1;
+                    stpmax = 1.0E50;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        if( (double)(state.d[i])<(double)(0.0) )
+                        {
+                            v = stpmax;
+                            stpmax = apserv.safeminposrv(state.xc[i], -state.d[i], stpmax, _params);
+                            if( (double)(stpmax)<(double)(v) )
+                            {
+                                actidx = i;
+                            }
+                        }
+                    }
+                    if( (double)(dnrm)==(double)(0.0) )
+                    {
+                        break;
+                    }
+                    
+                    //
+                    // Calculate trial function value at unconstrained full step.
+                    // If trial value is greater or equal to FC, terminate iterations.
+                    //
+                    for(i=0; i<=n-1; i++)
+                    {
+                        state.xn[i] = state.xc[i]+1.0*state.d[i];
+                    }
+                    qpcalculatefunc(sampleg, diagh, nsample, nvars, state.xn, ref state.fn, ref state.tmp0, _params);
+                    if( (double)(state.fn)>=(double)(state.fc) )
+                    {
+                        break;
+                    }
+                    
+                    //
+                    // Perform step
+                    // Update Hessian
+                    // Update XC
+                    //
+                    // Break if:
+                    // a) no constraint was activated
+                    // b) norm of D is small enough
+                    //
+                    stp = Math.Min(1.0, stpmax);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        state.xn[i] = Math.Max(state.xc[i]+stp*state.d[i], 0.0);
+                    }
+                    if( (double)(stp)==(double)(stpmax) && actidx>=0 )
+                    {
+                        state.xn[actidx] = 0.0;
+                    }
+                    wasactivation = false;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        state.tmpb[i] = (double)(state.xn[i])==(double)(0.0) && (double)(state.xc[i])!=(double)(0.0);
+                        wasactivation = wasactivation || state.tmpb[i];
+                    }
+                    for(i_=0; i_<=n-1;i_++)
+                    {
+                        state.xc[i_] = state.xn[i_];
+                    }
+                    if( !wasactivation )
+                    {
+                        break;
+                    }
+                    if( (double)(dnrm)<=(double)(dtol) )
+                    {
+                        break;
+                    }
+                    trfac.spdmatrixcholeskyupdatefixbuf(state.ch, n, true, state.tmpb, ref state.tmp0, _params);
+                }
+                
+                //
+                // Compare status of boundary constraints - if nothing changed during
+                // last outer iteration, TermCnt is increased. Otherwise it is reset
+                // to zero.
+                //
+                // When TermCnt is large enough, we terminate algorithm.
+                //
+                werechanges = false;
+                for(i=0; i<=n-1; i++)
+                {
+                    werechanges = werechanges || Math.Sign(state.x0[i])!=Math.Sign(state.xc[i]);
+                }
+                if( !werechanges )
+                {
+                    apserv.inc(ref termcnt, _params);
+                }
+                else
+                {
+                    termcnt = 0;
+                }
+                if( termcnt>=2 )
+                {
+                    break;
+                }
+                
+                //
+                // Increase number of outer iterations.
+                // Break if we performed too many.
+                //
+                apserv.inc(ref outerits, _params);
+                if( outerits==10 )
+                {
+                    break;
+                }
+            }
+            
+            //
+            // Store result
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                coeffs[i] = state.xc[i];
+            }
+        }
+
+
+        /*************************************************************************
+        Function/gradient calculation for QP solver.
+
+          -- ALGLIB --
+             Copyright 02.06.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static void qpcalculategradfunc(double[,] sampleg,
+            double[] diagh,
+            int nsample,
+            int nvars,
+            double[] coeffs,
+            ref double[] g,
+            ref double f,
+            ref double[] tmp,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int i_ = 0;
+
+            f = 0;
+
+            apserv.rvectorsetlengthatleast(ref g, nsample, _params);
+            apserv.rvectorsetlengthatleast(ref tmp, nvars, _params);
+            
+            //
+            // Calculate GS*p
+            //
+            for(j=0; j<=nvars-1; j++)
+            {
+                tmp[j] = 0.0;
+            }
+            for(i=0; i<=nsample-1; i++)
+            {
+                v = coeffs[i];
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    tmp[i_] = tmp[i_] + v*sampleg[i,i_];
+                }
+            }
+            
+            //
+            // Calculate F
+            //
+            f = 0.0;
+            for(i=0; i<=nvars-1; i++)
+            {
+                f = f+0.5*math.sqr(tmp[i])/diagh[i];
+            }
+            
+            //
+            // Multiply by inverse Hessian
+            //
+            for(i=0; i<=nvars-1; i++)
+            {
+                tmp[i] = tmp[i]/diagh[i];
+            }
+            
+            //
+            // Function gradient
+            //
+            for(i=0; i<=nsample-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    v += sampleg[i,i_]*tmp[i_];
+                }
+                g[i] = v;
+            }
+        }
+
+
+        /*************************************************************************
+        Function calculation for QP solver.
+
+          -- ALGLIB --
+             Copyright 02.06.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static void qpcalculatefunc(double[,] sampleg,
+            double[] diagh,
+            int nsample,
+            int nvars,
+            double[] coeffs,
+            ref double f,
+            ref double[] tmp,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            int i_ = 0;
+
+            f = 0;
+
+            apserv.rvectorsetlengthatleast(ref tmp, nvars, _params);
+            
+            //
+            // Calculate GS*p
+            //
+            for(j=0; j<=nvars-1; j++)
+            {
+                tmp[j] = 0.0;
+            }
+            for(i=0; i<=nsample-1; i++)
+            {
+                v = coeffs[i];
+                for(i_=0; i_<=nvars-1;i_++)
+                {
+                    tmp[i_] = tmp[i_] + v*sampleg[i,i_];
+                }
+            }
+            
+            //
+            // Calculate F
+            //
+            f = 0.0;
+            for(i=0; i<=nvars-1; i++)
+            {
+                f = f+0.5*math.sqr(tmp[i])/diagh[i];
+            }
+        }
+
+
+        /*************************************************************************
+        Triangular solver for QP solver.
+
+          -- ALGLIB --
+             Copyright 02.06.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static void qpsolveu(double[,] a,
+            int n,
+            double[] x,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            
+            //
+            // A^(-1)*X
+            //
+            for(i=n-1; i>=0; i--)
+            {
+                v = x[i];
+                for(j=i+1; j<=n-1; j++)
+                {
+                    v = v-a[i,j]*x[j];
+                }
+                x[i] = v/a[i,i];
+            }
+        }
+
+
+        /*************************************************************************
+        Triangular solver for QP solver.
+
+          -- ALGLIB --
+             Copyright 02.06.2015 by Bochkanov Sergey
+        *************************************************************************/
+        private static void qpsolveut(double[,] a,
+            int n,
+            double[] x,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+
+            
+            //
+            // A^(-T)*X
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                x[i] = x[i]/a[i,i];
+                v = x[i];
+                for(j=i+1; j<=n-1; j++)
+                {
+                    x[j] = x[j]-a[i,j]*v;
+                }
+            }
+        }
+
+
+    }
+    public class mincomp
+    {
+        public class minasastate : apobject
+        {
+            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()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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 override alglib.apobject make_copy()
+            {
+                minasastate _result = new minasastate();
+                _result.n = n;
+                _result.epsg = epsg;
+                _result.epsf = epsf;
+                _result.epsx = epsx;
+                _result.maxits = maxits;
+                _result.xrep = xrep;
+                _result.stpmax = stpmax;
+                _result.cgtype = cgtype;
+                _result.k = k;
+                _result.nfev = nfev;
+                _result.mcstage = mcstage;
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.curalgo = curalgo;
+                _result.acount = acount;
+                _result.mu = mu;
+                _result.finit = finit;
+                _result.dginit = dginit;
+                _result.ak = (double[])ak.Clone();
+                _result.xk = (double[])xk.Clone();
+                _result.dk = (double[])dk.Clone();
+                _result.an = (double[])an.Clone();
+                _result.xn = (double[])xn.Clone();
+                _result.dn = (double[])dn.Clone();
+                _result.d = (double[])d.Clone();
+                _result.fold = fold;
+                _result.stp = stp;
+                _result.work = (double[])work.Clone();
+                _result.yk = (double[])yk.Clone();
+                _result.gc = (double[])gc.Clone();
+                _result.laststep = laststep;
+                _result.x = (double[])x.Clone();
+                _result.f = f;
+                _result.g = (double[])g.Clone();
+                _result.needfg = needfg;
+                _result.xupdated = xupdated;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.repiterationscount = repiterationscount;
+                _result.repnfev = repnfev;
+                _result.repterminationtype = repterminationtype;
+                _result.debugrestartscount = debugrestartscount;
+                _result.lstate = (linmin.linminstate)lstate.make_copy();
+                _result.betahs = betahs;
+                _result.betady = betady;
+                return _result;
+            }
+        };
+
+
+        public class minasareport : apobject
+        {
+            public int iterationscount;
+            public int nfev;
+            public int terminationtype;
+            public int activeconstraints;
+            public minasareport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                minasareport _result = new minasareport();
+                _result.iterationscount = iterationscount;
+                _result.nfev = nfev;
+                _result.terminationtype = terminationtype;
+                _result.activeconstraints = activeconstraints;
+                return _result;
+            }
+        };
+
+
+
+
+        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,
+            alglib.xparams _params)
+        {
+            minlbfgs.minlbfgssetprecdefault(state, _params);
+        }
+
+
+        /*************************************************************************
+        Obsolete function, use MinLBFGSSetCholeskyPreconditioner() instead.
+
+          -- ALGLIB --
+             Copyright 13.10.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlbfgssetcholeskypreconditioner(minlbfgs.minlbfgsstate state,
+            double[,] p,
+            bool isupper,
+            alglib.xparams _params)
+        {
+            minlbfgs.minlbfgssetpreccholesky(state, p, isupper, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, _params), "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, _params);
+            minasasetxrep(state, false, _params);
+            minasasetstpmax(state, 0, _params);
+            minasasetalgorithm(state, -1, _params);
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 = 359;
+                i = -58;
+                mcinfo = -919;
+                diffcnt = -909;
+                b = true;
+                stepfound = true;
+                betak = 74;
+                v = -788;
+                vv = 809;
+            }
+            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, _params);
+            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, _params);
+            state.xupdated = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.xupdated = false;
+        lbl_15:
+            if( (double)(asaboundedantigradnorm(state, _params))<=(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], _params)-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, _params))<=(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], _params);
+            }
+            clearrequestfields(state, _params);
+            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, _params);
+            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, _params);
+            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, _params);
+            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, _params);
+            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, _params))>(double)(state.epsg) )
+            {
+                goto lbl_35;
+            }
+            
+            //
+            // Gradient is small enough
+            //
+            state.repterminationtype = 4;
+            if( !state.xrep )
+            {
+                goto lbl_37;
+            }
+            clearrequestfields(state, _params);
+            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, _params);
+            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, _params);
+            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, _params) )
+            {
+                if( (double)(asaginorm(state, _params))>=(double)(state.mu*asad1norm(state, _params)) )
+                {
+                    state.curalgo = 1;
+                    goto lbl_22;
+                }
+                else
+                {
+                    state.mu = state.mu*asarho;
+                }
+            }
+            else
+            {
+                if( state.acount==n1 )
+                {
+                    if( (double)(asaginorm(state, _params))>=(double)(state.mu*asad1norm(state, _params)) )
+                    {
+                        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, _params);
+            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, _params);
+        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], _params);
+            }
+            
+            //
+            // RComm
+            //
+            clearrequestfields(state, _params);
+            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, _params);
+            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], _params);
+                
+                //
+                // 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, _params);
+            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, _params))>(double)(state.epsg) )
+            {
+                goto lbl_55;
+            }
+            
+            //
+            // Gradient is small enough
+            //
+            state.repterminationtype = 4;
+            if( !state.xrep )
+            {
+                goto lbl_57;
+            }
+            clearrequestfields(state, _params);
+            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, _params);
+            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, _params))>=(double)(state.mu*asad1norm(state, _params)) && 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, _params);
+            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, _params);
+            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, _params))<(double)(state.mu*asad1norm(state, _params)) )
+            {
+                state.curalgo = 0;
+                goto lbl_50;
+            }
+            if( diffcnt>0 )
+            {
+                if( asauisempty(state, _params) || 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,
+            alglib.xparams _params)
+        {
+            x = new double[0];
+
+            minasaresultsbuf(state, ref x, rep, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=state.n, "MinASARestartFrom: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, state.n, _params), "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, _params), "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, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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], _params)-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,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+            double d = 0;
+            double d2 = 0;
+            double d32 = 0;
+
+            d = asad1norm(state, _params);
+            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,
+            alglib.xparams _params)
+        {
+            state.needfg = false;
+            state.xupdated = false;
+        }
+
+
+    }
+    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 : apobject
+        {
+            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 double[] xbase;
+            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 bool userterminationneeded;
+            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 double[] invs;
+            public double teststep;
+            public int smoothnessguardlevel;
+            public optserv.smoothnessmonitor smonitor;
+            public double[] lastscaleused;
+            public mincgstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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];
+                xbase = 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];
+                invs = new double[0];
+                smonitor = new optserv.smoothnessmonitor();
+                lastscaleused = new double[0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                mincgstate _result = new mincgstate();
+                _result.n = n;
+                _result.epsg = epsg;
+                _result.epsf = epsf;
+                _result.epsx = epsx;
+                _result.maxits = maxits;
+                _result.stpmax = stpmax;
+                _result.suggestedstep = suggestedstep;
+                _result.xrep = xrep;
+                _result.drep = drep;
+                _result.cgtype = cgtype;
+                _result.prectype = prectype;
+                _result.diagh = (double[])diagh.Clone();
+                _result.diaghl2 = (double[])diaghl2.Clone();
+                _result.vcorr = (double[,])vcorr.Clone();
+                _result.vcnt = vcnt;
+                _result.s = (double[])s.Clone();
+                _result.diffstep = diffstep;
+                _result.nfev = nfev;
+                _result.mcstage = mcstage;
+                _result.k = k;
+                _result.xk = (double[])xk.Clone();
+                _result.dk = (double[])dk.Clone();
+                _result.xn = (double[])xn.Clone();
+                _result.dn = (double[])dn.Clone();
+                _result.d = (double[])d.Clone();
+                _result.fold = fold;
+                _result.stp = stp;
+                _result.curstpmax = curstpmax;
+                _result.yk = (double[])yk.Clone();
+                _result.lastgoodstep = lastgoodstep;
+                _result.lastscaledstep = lastscaledstep;
+                _result.mcinfo = mcinfo;
+                _result.innerresetneeded = innerresetneeded;
+                _result.terminationneeded = terminationneeded;
+                _result.trimthreshold = trimthreshold;
+                _result.xbase = (double[])xbase.Clone();
+                _result.rstimer = rstimer;
+                _result.x = (double[])x.Clone();
+                _result.f = f;
+                _result.g = (double[])g.Clone();
+                _result.needf = needf;
+                _result.needfg = needfg;
+                _result.xupdated = xupdated;
+                _result.algpowerup = algpowerup;
+                _result.lsstart = lsstart;
+                _result.lsend = lsend;
+                _result.userterminationneeded = userterminationneeded;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.repiterationscount = repiterationscount;
+                _result.repnfev = repnfev;
+                _result.repterminationtype = repterminationtype;
+                _result.debugrestartscount = debugrestartscount;
+                _result.lstate = (linmin.linminstate)lstate.make_copy();
+                _result.fbase = fbase;
+                _result.fm2 = fm2;
+                _result.fm1 = fm1;
+                _result.fp1 = fp1;
+                _result.fp2 = fp2;
+                _result.betahs = betahs;
+                _result.betady = betady;
+                _result.work0 = (double[])work0.Clone();
+                _result.work1 = (double[])work1.Clone();
+                _result.invs = (double[])invs.Clone();
+                _result.teststep = teststep;
+                _result.smoothnessguardlevel = smoothnessguardlevel;
+                _result.smonitor = (optserv.smoothnessmonitor)smonitor.make_copy();
+                _result.lastscaleused = (double[])lastscaleused.Clone();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        This structure stores optimization report:
+        * IterationsCount           total number of inner iterations
+        * NFEV                      number of gradient evaluations
+        * TerminationType           termination type (see below)
+
+        TERMINATION CODES
+
+        TerminationType field contains completion code, which can be:
+          -8    internal integrity control detected  infinite  or  NAN  values  in
+                function/gradient. Abnormal termination signalled.
+           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,
+                X contains best point found so far.
+           8    terminated by user who called mincgrequesttermination(). X contains
+                point which was "current accepted" when  termination  request  was
+                submitted.
+                
+        Other fields of this structure are not documented and should not be used!
+        *************************************************************************/
+        public class mincgreport : apobject
+        {
+            public int iterationscount;
+            public int nfev;
+            public int terminationtype;
+            public mincgreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                mincgreport _result = new mincgreport();
+                _result.iterationscount = iterationscount;
+                _result.nfev = nfev;
+                _result.terminationtype = terminationtype;
+                return _result;
+            }
+        };
+
+
+
+
+        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.xparams _params)
+        {
+            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, _params), "MinCGCreate: X contains infinite or NaN values!");
+            mincginitinternal(n, 0.0, state, _params);
+            mincgrestartfrom(state, x, _params);
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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, _params), "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, _params);
+            mincgrestartfrom(state, x, _params);
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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;
+        }
+
+
+        /*************************************************************************
+        This developer-only function allows to retrieve  unscaled  (!)  length  of
+        last good step (i.e. step which resulted in sufficient decrease of  target
+        function).
+
+        It can be used in for solution  of  sequential  optimization  subproblems,
+        where MinCGSuggestStep()  is  called  with  length  of  previous  step  as
+        parameter.
+
+        INPUT PARAMETERS:
+            State   -   structure used to store algorithm state.
+            
+        RESULT:
+            length of last good step being accepted
+            
+        NOTE:
+            result of this function is undefined if you called it before
+
+          -- ALGLIB --
+             Copyright 30.07.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static double mincglastgoodstep(mincgstate state,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = state.lastgoodstep;
+            return result;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 = 359;
+                i = -58;
+                betak = -919;
+                v = -909;
+                vv = 81;
+            }
+            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;
+            }
+            if( state.rstate.stage==17 )
+            {
+                goto lbl_17;
+            }
+            
+            //
+            // Routine body
+            //
+            
+            //
+            // Prepare
+            //
+            n = state.n;
+            state.terminationneeded = false;
+            state.userterminationneeded = false;
+            state.repterminationtype = 0;
+            state.repiterationscount = 0;
+            state.repnfev = 0;
+            state.debugrestartscount = 0;
+            optserv.smoothnessmonitorinit(state.smonitor, n, 1, state.smoothnessguardlevel>0, _params);
+            apserv.rvectorsetlengthatleast(ref state.invs, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.lastscaleused[i] = state.s[i];
+                state.invs[i] = 1/state.s[i];
+            }
+            
+            //
+            //  Check, that transferred derivative value is right
+            //
+            clearrequestfields(state, _params);
+            if( !((double)(state.diffstep)==(double)(0) && (double)(state.teststep)>(double)(0)) )
+            {
+                goto lbl_18;
+            }
+        lbl_20:
+            if( !optserv.smoothnessmonitorcheckgradientatx0(state.smonitor, state.xbase, state.s, state.s, state.s, false, state.teststep, _params) )
+            {
+                goto lbl_21;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.smonitor.x[i];
+            }
+            state.needfg = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfg = false;
+            state.smonitor.fi[0] = state.f;
+            for(i=0; i<=n-1; i++)
+            {
+                state.smonitor.j[0,i] = state.g[i];
+            }
+            goto lbl_20;
+        lbl_21:
+        lbl_18:
+            
+            //
+            // 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.x[i] = state.xbase[i];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xk[i_] = state.x[i_];
+            }
+            clearrequestfields(state, _params);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_22;
+            }
+            state.needfg = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.needfg = false;
+            goto lbl_23;
+        lbl_22:
+            state.needf = true;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.fbase = state.f;
+            i = 0;
+        lbl_24:
+            if( i>n-1 )
+            {
+                goto lbl_26;
+            }
+            v = state.x[i];
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 4;
+            goto lbl_rcomm;
+        lbl_4:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            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_24;
+        lbl_26:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_23:
+            if( !state.drep )
+            {
+                goto lbl_27;
+            }
+            
+            //
+            // Report algorithm powerup (if needed)
+            //
+            clearrequestfields(state, _params);
+            state.algpowerup = true;
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.algpowerup = false;
+        lbl_27:
+            optserv.trimprepare(state.f, ref state.trimthreshold, _params);
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.dk[i_] = -state.g[i_];
+            }
+            preconditionedmultiply(state, ref state.dk, ref state.work0, ref state.work1, _params);
+            if( !state.xrep )
+            {
+                goto lbl_29;
+            }
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 8;
+            goto lbl_rcomm;
+        lbl_8:
+            state.xupdated = false;
+        lbl_29:
+            if( state.terminationneeded || state.userterminationneeded )
+            {
+                
+                //
+                // Combined termination point for "internal" termination by TerminationNeeded flag
+                // and for "user" termination by MinCGRequestTermination() (UserTerminationNeeded flag).
+                // In this location rules for both of methods are same, thus only one exit point is needed.
+                //
+                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_31:
+            if( false )
+            {
+                goto lbl_32;
+            }
+            
+            //
+            // * 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, _params);
+            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_33;
+            }
+            clearrequestfields(state, _params);
+            state.lsstart = true;
+            state.rstate.stage = 9;
+            goto lbl_rcomm;
+        lbl_9:
+            state.lsstart = false;
+        lbl_33:
+            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
+            //
+            optserv.smoothnessmonitorstartlinesearch1u(state.smonitor, state.s, state.invs, state.x, state.f, state.g, _params);
+            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, _params);
+        lbl_35:
+            if( state.mcstage==0 )
+            {
+                goto lbl_36;
+            }
+            
+            //
+            // 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, _params);
+            if( (double)(state.diffstep)!=(double)(0) )
+            {
+                goto lbl_37;
+            }
+            state.needfg = true;
+            state.rstate.stage = 10;
+            goto lbl_rcomm;
+        lbl_10:
+            state.needfg = false;
+            goto lbl_38;
+        lbl_37:
+            state.needf = true;
+            state.rstate.stage = 11;
+            goto lbl_rcomm;
+        lbl_11:
+            state.fbase = state.f;
+            i = 0;
+        lbl_39:
+            if( i>n-1 )
+            {
+                goto lbl_41;
+            }
+            v = state.x[i];
+            state.x[i] = v-state.diffstep*state.s[i];
+            state.rstate.stage = 12;
+            goto lbl_rcomm;
+        lbl_12:
+            state.fm2 = state.f;
+            state.x[i] = v-0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 13;
+            goto lbl_rcomm;
+        lbl_13:
+            state.fm1 = state.f;
+            state.x[i] = v+0.5*state.diffstep*state.s[i];
+            state.rstate.stage = 14;
+            goto lbl_rcomm;
+        lbl_14:
+            state.fp1 = state.f;
+            state.x[i] = v+state.diffstep*state.s[i];
+            state.rstate.stage = 15;
+            goto lbl_rcomm;
+        lbl_15:
+            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_39;
+        lbl_41:
+            state.f = state.fbase;
+            state.needf = false;
+        lbl_38:
+            optserv.smoothnessmonitorenqueuepoint1u(state.smonitor, state.s, state.invs, state.d, state.stp, state.x, state.f, state.g, _params);
+            optserv.trimfunction(ref state.f, ref state.g, n, state.trimthreshold, _params);
+            
+            //
+            // 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, _params);
+            goto lbl_35;
+        lbl_36:
+            optserv.smoothnessmonitorfinalizelinesearch(state.smonitor, _params);
+            
+            //
+            // * terminate algorithm if "user" request for detected
+            // * report end of line search
+            // * store current point to XN
+            // * report iteration
+            // * terminate algorithm if "internal" request was detected
+            //
+            if( state.userterminationneeded )
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.xn[i_] = state.xk[i_];
+                }
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            if( !state.drep )
+            {
+                goto lbl_42;
+            }
+            
+            //
+            // Report end of line search (if needed)
+            //
+            clearrequestfields(state, _params);
+            state.lsend = true;
+            state.rstate.stage = 16;
+            goto lbl_rcomm;
+        lbl_16:
+            state.lsend = false;
+        lbl_42:
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xn[i_] = state.x[i_];
+            }
+            if( !state.xrep )
+            {
+                goto lbl_44;
+            }
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 17;
+            goto lbl_rcomm;
+        lbl_17:
+            state.xupdated = false;
+        lbl_44:
+            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, _params);
+                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, _params);
+                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, _params);
+            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.
+            //
+            v = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(state.g[i]*state.s[i]);
+            }
+            if( !math.isfinite(v) || !math.isfinite(state.f) )
+            {
+                
+                //
+                // Abnormal termination - infinities in function/gradient
+                //
+                state.repterminationtype = -8;
+                result = false;
+                return result;
+            }
+            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;
+            }
+            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_31;
+        lbl_32:
+            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;
+        }
+
+
+        /*************************************************************************
+        This  function  activates/deactivates verification  of  the  user-supplied
+        analytic gradient.
+
+        Upon  activation  of  this  option  OptGuard  integrity  checker  performs
+        numerical differentiation of your target function  at  the  initial  point
+        (note: future versions may also perform check  at  the  final  point)  and
+        compares numerical gradient with analytic one provided by you.
+
+        If difference is too large, an error flag is set and optimization  session
+        continues. After optimization session is over, you can retrieve the report
+        which  stores  both  gradients  and  specific  components  highlighted  as
+        suspicious by the OptGuard.
+
+        The primary OptGuard report can be retrieved with mincgoptguardresults().
+
+        IMPORTANT: gradient check is a high-overhead option which  will  cost  you
+                   about 3*N additional function evaluations. In many cases it may
+                   cost as much as the rest of the optimization session.
+                   
+                   YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO
+                   CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY.
+
+        NOTE: unlike previous incarnation of the gradient checking code,  OptGuard
+              does NOT interrupt optimization even if it discovers bad gradient.
+              
+        INPUT PARAMETERS:
+            State       -   structure used to store algorithm state
+            TestStep    -   verification step used for numerical differentiation:
+                            * TestStep=0 turns verification off
+                            * TestStep>0 activates verification
+                            You should carefully choose TestStep. Value  which  is
+                            too large (so large that  function  behavior  is  non-
+                            cubic at this scale) will lead  to  false  alarms. Too
+                            short step will result in rounding  errors  dominating
+                            numerical derivative.
+                            
+                            You may use different step for different parameters by
+                            means of setting scale with mincgsetscale().
+
+        === EXPLANATION ==========================================================                    
+
+        In order to verify gradient algorithm performs following steps:
+          * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i],
+            where X[i] is i-th component of the initial point and S[i] is a  scale
+            of i-th parameter
+          * F(X) is evaluated at these trial points
+          * we perform one more evaluation in the middle point of the interval
+          * we  build  cubic  model using function values and derivatives at trial
+            points and we compare its prediction with actual value in  the  middle
+            point
+
+          -- ALGLIB --
+             Copyright 15.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgoptguardgradient(mincgstate state,
+            double teststep,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(teststep), "MinCGOptGuardGradient: TestStep contains NaN or INF");
+            alglib.ap.assert((double)(teststep)>=(double)(0), "MinCGOptGuardGradient: invalid argument TestStep(TestStep<0)");
+            state.teststep = teststep;
+        }
+
+
+        /*************************************************************************
+        This  function  activates/deactivates nonsmoothness monitoring  option  of
+        the  OptGuard  integrity  checker. Smoothness  monitor  silently  observes
+        solution process and tries to detect ill-posed problems, i.e. ones with:
+        a) discontinuous target function (non-C0)
+        b) nonsmooth     target function (non-C1)
+
+        Smoothness monitoring does NOT interrupt optimization  even if it suspects
+        that your problem is nonsmooth. It just sets corresponding  flags  in  the
+        OptGuard report which can be retrieved after optimization is over.
+
+        Smoothness monitoring is a moderate overhead option which often adds  less
+        than 1% to the optimizer running time. Thus, you can use it even for large
+        scale problems.
+
+        NOTE: OptGuard does  NOT  guarantee  that  it  will  always  detect  C0/C1
+              continuity violations.
+              
+              First, minor errors are hard to  catch - say, a 0.0001 difference in
+              the model values at two sides of the gap may be due to discontinuity
+              of the model - or simply because the model has changed.
+              
+              Second, C1-violations  are  especially  difficult  to  detect  in  a
+              noninvasive way. The optimizer usually  performs  very  short  steps
+              near the nonsmoothness, and differentiation  usually   introduces  a
+              lot of numerical noise.  It  is  hard  to  tell  whether  some  tiny
+              discontinuity in the slope is due to real nonsmoothness or just  due
+              to numerical noise alone.
+              
+              Our top priority was to avoid false positives, so in some rare cases
+              minor errors may went unnoticed (however, in most cases they can  be
+              spotted with restart from different initial point).
+
+        INPUT PARAMETERS:
+            state   -   algorithm state
+            level   -   monitoring level:
+                        * 0 - monitoring is disabled
+                        * 1 - noninvasive low-overhead monitoring; function values
+                              and/or gradients are recorded, but OptGuard does not
+                              try to perform additional evaluations  in  order  to
+                              get more information about suspicious locations.
+          
+        === EXPLANATION ==========================================================
+
+        One major source of headache during optimization  is  the  possibility  of
+        the coding errors in the target function/constraints (or their gradients).
+        Such  errors   most   often   manifest   themselves  as  discontinuity  or
+        nonsmoothness of the target/constraints.
+
+        Another frequent situation is when you try to optimize something involving
+        lots of min() and max() operations, i.e. nonsmooth target. Although not  a
+        coding error, it is nonsmoothness anyway - and smooth  optimizers  usually
+        stop right after encountering nonsmoothness, well before reaching solution.
+
+        OptGuard integrity checker helps you to catch such situations: it monitors
+        function values/gradients being passed  to  the  optimizer  and  tries  to
+        errors. Upon discovering suspicious pair of points it  raises  appropriate
+        flag (and allows you to continue optimization). When optimization is done,
+        you can study OptGuard result.
+
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgoptguardsmoothness(mincgstate state,
+            int level,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(level==0 || level==1, "MinCGOptGuardSmoothness: unexpected value of level parameter");
+            state.smoothnessguardlevel = level;
+        }
+
+
+        /*************************************************************************
+        Results of OptGuard integrity check, should be called  after  optimization
+        session is over.
+
+        === PRIMARY REPORT =======================================================
+
+        OptGuard performs several checks which are intended to catch common errors
+        in the implementation of nonlinear function/gradient:
+        * incorrect analytic gradient
+        * discontinuous (non-C0) target functions (constraints)
+        * nonsmooth     (non-C1) target functions (constraints)
+
+        Each of these checks is activated with appropriate function:
+        * mincgoptguardgradient() for gradient verification
+        * mincgoptguardsmoothness() for C0/C1 checks
+
+        Following flags are set when these errors are suspected:
+        * rep.badgradsuspected, and additionally:
+          * rep.badgradvidx for specific variable (gradient element) suspected
+          * rep.badgradxbase, a point where gradient is tested
+          * rep.badgraduser, user-provided gradient  (stored  as  2D  matrix  with
+            single row in order to make  report  structure  compatible  with  more
+            complex optimizers like MinNLC or MinLM)
+          * rep.badgradnum,   reference    gradient    obtained    via   numerical
+            differentiation (stored as  2D matrix with single row in order to make
+            report structure compatible with more complex optimizers  like  MinNLC
+            or MinLM)
+        * rep.nonc0suspected
+        * rep.nonc1suspected
+
+        === ADDITIONAL REPORTS/LOGS ==============================================
+            
+        Several different tests are performed to catch C0/C1 errors, you can  find
+        out specific test signaled error by looking to:
+        * rep.nonc0test0positive, for non-C0 test #0
+        * rep.nonc1test0positive, for non-C1 test #0
+        * rep.nonc1test1positive, for non-C1 test #1
+
+        Additional information (including line search logs)  can  be  obtained  by
+        means of:
+        * mincgoptguardnonc1test0results()
+        * mincgoptguardnonc1test1results()
+        which return detailed error reports, specific points where discontinuities
+        were found, and so on.
+
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            rep     -   generic OptGuard report;  more  detailed  reports  can  be
+                        retrieved with other functions.
+
+        NOTE: false negatives (nonsmooth problems are not identified as  nonsmooth
+              ones) are possible although unlikely.
+              
+              The reason  is  that  you  need  to  make several evaluations around
+              nonsmoothness  in  order  to  accumulate  enough  information  about
+              function curvature. Say, if you start right from the nonsmooth point,
+              optimizer simply won't get enough data to understand what  is  going
+              wrong before it terminates due to abrupt changes in the  derivative.
+              It is also  possible  that  "unlucky"  step  will  move  us  to  the
+              termination too quickly.
+              
+              Our current approach is to have less than 0.1%  false  negatives  in
+              our test examples  (measured  with  multiple  restarts  from  random
+              points), and to have exactly 0% false positives.
+           
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgoptguardresults(mincgstate state,
+            optguardapi.optguardreport rep,
+            alglib.xparams _params)
+        {
+            optserv.smoothnessmonitorexportreport(state.smonitor, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Detailed results of the OptGuard integrity check for nonsmoothness test #0
+
+        Nonsmoothness (non-C1) test #0 studies  function  values  (not  gradient!)
+        obtained during line searches and monitors  behavior  of  the  directional
+        derivative estimate.
+
+        This test is less powerful than test #1, but it does  not  depend  on  the
+        gradient values and thus it is more robust against artifacts introduced by
+        numerical differentiation.
+
+        Two reports are returned:
+        * a "strongest" one, corresponding  to  line   search  which  had  highest
+          value of the nonsmoothness indicator
+        * a "longest" one, corresponding to line search which  had  more  function
+          evaluations, and thus is more detailed
+
+        In both cases following fields are returned:
+
+        * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+          did not notice anything (in the latter cases fields below are empty).
+        * x0[], d[] - arrays of length N which store initial point  and  direction
+          for line search (d[] can be normalized, but does not have to)
+        * stp[], f[] - arrays of length CNT which store step lengths and  function
+          values at these points; f[i] is evaluated in x0+stp[i]*d.
+        * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+          between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+          with  most  likely  position  of  the  violation  between  stpidxa+1 and
+          stpidxa+2.
+          
+        ==========================================================================
+        = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+        =                   see where C1 continuity is violated.
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            strrep  -   C1 test #0 "strong" report
+            lngrep  -   C1 test #0 "long" report
+            
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgoptguardnonc1test0results(mincgstate state,
+            optguardapi.optguardnonc1test0report strrep,
+            optguardapi.optguardnonc1test0report lngrep,
+            alglib.xparams _params)
+        {
+            optguardapi.smoothnessmonitorexportc1test0report(state.smonitor.nonc1test0strrep, state.lastscaleused, strrep, _params);
+            optguardapi.smoothnessmonitorexportc1test0report(state.smonitor.nonc1test0lngrep, state.lastscaleused, lngrep, _params);
+        }
+
+
+        /*************************************************************************
+        Detailed results of the OptGuard integrity check for nonsmoothness test #1
+
+        Nonsmoothness (non-C1)  test  #1  studies  individual  components  of  the
+        gradient computed during line search.
+
+        When precise analytic gradient is provided this test is more powerful than
+        test #0  which  works  with  function  values  and  ignores  user-provided
+        gradient.  However,  test  #0  becomes  more   powerful   when   numerical
+        differentiation is employed (in such cases test #1 detects  higher  levels
+        of numerical noise and becomes too conservative).
+
+        This test also tells specific components of the gradient which violate  C1
+        continuity, which makes it more informative than #0, which just tells that
+        continuity is violated.
+
+        Two reports are returned:
+        * a "strongest" one, corresponding  to  line   search  which  had  highest
+          value of the nonsmoothness indicator
+        * a "longest" one, corresponding to line search which  had  more  function
+          evaluations, and thus is more detailed
+
+        In both cases following fields are returned:
+
+        * positive - is TRUE  when test flagged suspicious point;  FALSE  if  test
+          did not notice anything (in the latter cases fields below are empty).
+        * vidx - is an index of the variable in [0,N) with nonsmooth derivative
+        * x0[], d[] - arrays of length N which store initial point  and  direction
+          for line search (d[] can be normalized, but does not have to)
+        * stp[], g[] - arrays of length CNT which store step lengths and  gradient
+          values at these points; g[i] is evaluated in  x0+stp[i]*d  and  contains
+          vidx-th component of the gradient.
+        * stpidxa, stpidxb - we  suspect  that  function  violates  C1  continuity
+          between steps #stpidxa and #stpidxb (usually we have  stpidxb=stpidxa+3,
+          with  most  likely  position  of  the  violation  between  stpidxa+1 and
+          stpidxa+2.
+          
+        ==========================================================================
+        = SHORTLY SPEAKING: build a 2D plot of (stp,f) and look at it -  you  will
+        =                   see where C1 continuity is violated.
+        ==========================================================================
+             
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            strrep  -   C1 test #1 "strong" report
+            lngrep  -   C1 test #1 "long" report
+            
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgoptguardnonc1test1results(mincgstate state,
+            optguardapi.optguardnonc1test1report strrep,
+            optguardapi.optguardnonc1test1report lngrep,
+            alglib.xparams _params)
+        {
+            optguardapi.smoothnessmonitorexportc1test1report(state.smonitor.nonc1test1strrep, state.lastscaleused, strrep, _params);
+            optguardapi.smoothnessmonitorexportc1test1report(state.smonitor.nonc1test1lngrep, state.lastscaleused, lngrep, _params);
+        }
+
+
+        /*************************************************************************
+        Conjugate gradient results
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[0..N-1], solution
+            Rep     -   optimization report:
+                        * Rep.TerminationType completetion code:
+                            * -8    internal integrity control  detected  infinite
+                                    or NAN values in  function/gradient.  Abnormal
+                                    termination signalled.
+                            * -7    gradient verification failed.
+                                    See MinCGSetGradientCheck() for more information.
+                            *  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,
+            alglib.xparams _params)
+        {
+            x = new double[0];
+
+            mincgresultsbuf(state, ref x, rep, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=state.n, "MinCGRestartFrom: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, state.n, _params), "MinCGCreate: X contains infinite or NaN values!");
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.xbase[i_] = x[i_];
+            }
+            mincgsuggeststep(state, 0.0, _params);
+            state.rstate.ia = new int[1+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine submits request for termination of running  optimizer.  It
+        should be called from user-supplied callback when user decides that it  is
+        time to "smoothly" terminate optimization process.  As  result,  optimizer
+        stops at point which was "current accepted" when termination  request  was
+        submitted and returns error code 8 (successful termination).
+
+        INPUT PARAMETERS:
+            State   -   optimizer structure
+
+        NOTE: after  request  for  termination  optimizer  may   perform   several
+              additional calls to user-supplied callbacks. It does  NOT  guarantee
+              to stop immediately - it just guarantees that these additional calls
+              will be discarded later.
+
+        NOTE: calling this function on optimizer which is NOT running will have no
+              effect.
+              
+        NOTE: multiple calls to this function are possible. First call is counted,
+              subsequent calls are silently ignored.
+
+          -- ALGLIB --
+             Copyright 08.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void mincgrequesttermination(mincgstate state,
+            alglib.xparams _params)
+        {
+            state.userterminationneeded = true;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            apserv.rvectorsetlengthatleast(ref state.diagh, state.n, _params);
+            apserv.rvectorsetlengthatleast(ref state.diaghl2, state.n, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                return;
+            }
+            n = state.n;
+            b = new double[vcnt, vcnt];
+            apserv.rvectorsetlengthatleast(ref state.diagh, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.diaghl2, n, _params);
+            apserv.rmatrixsetlengthatleast(ref state.vcorr, vcnt, n, _params);
+            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, _params) )
+            {
+                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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            
+            //
+            // Initialize
+            //
+            state.teststep = 0;
+            state.smoothnessguardlevel = 0;
+            optserv.smoothnessmonitorinit(state.smonitor, 0, 0, false, _params);
+            state.n = n;
+            state.diffstep = diffstep;
+            state.lastgoodstep = 0;
+            mincgsetcond(state, 0, 0, 0, 0, _params);
+            mincgsetxrep(state, false, _params);
+            mincgsetdrep(state, false, _params);
+            mincgsetstpmax(state, 0, _params);
+            mincgsetcgtype(state, -1, _params);
+            mincgsetprecdefault(state, _params);
+            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];
+            state.invs = new double[n];
+            state.lastscaleused = new double[n];
+            apserv.rvectorsetlengthatleast(ref state.xbase, n, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                state.s[i] = 1.0;
+                state.invs[i] = 1.0;
+                state.lastscaleused[i] = 1.0;
+            }
+        }
+
+
+    }
+    public class minlm
+    {
+        /*************************************************************************
+        Step finder for Levenberg-Marquardt optimizer.
+        Internal object used by MinLM unit.
+
+        This structure should be initialized with MinLMStepFinderInit(). Each step
+        search session should start  with  MinLMStepFinderStart()  call,  followed
+        by a sequence of MinLMStepFinderIteration() calls.
+        *************************************************************************/
+        public class minlmstepfinder : apobject
+        {
+            public int n;
+            public int m;
+            public double stpmax;
+            public int modelage;
+            public int maxmodelage;
+            public bool hasfi;
+            public double epsx;
+            public double[] x;
+            public double f;
+            public double[] fi;
+            public bool needf;
+            public bool needfi;
+            public double fbase;
+            public double[] modeldiag;
+            public double[] xbase;
+            public double[] fibase;
+            public double[] bndl;
+            public double[] bndu;
+            public bool[] havebndl;
+            public bool[] havebndu;
+            public double[] s;
+            public rcommstate rstate;
+            public double[] xdir;
+            public double[] choleskybuf;
+            public double[] tmp0;
+            public int[] tmpct;
+            public double actualdecrease;
+            public double predicteddecrease;
+            public minqp.minqpstate qpstate;
+            public minqp.minqpreport qprep;
+            public sparse.sparsematrix tmpsp;
+            public minlmstepfinder()
+            {
+                init();
+            }
+            public override void init()
+            {
+                x = new double[0];
+                fi = new double[0];
+                modeldiag = new double[0];
+                xbase = new double[0];
+                fibase = new double[0];
+                bndl = new double[0];
+                bndu = new double[0];
+                havebndl = new bool[0];
+                havebndu = new bool[0];
+                s = new double[0];
+                rstate = new rcommstate();
+                xdir = new double[0];
+                choleskybuf = new double[0];
+                tmp0 = new double[0];
+                tmpct = new int[0];
+                qpstate = new minqp.minqpstate();
+                qprep = new minqp.minqpreport();
+                tmpsp = new sparse.sparsematrix();
+            }
+            public override alglib.apobject make_copy()
+            {
+                minlmstepfinder _result = new minlmstepfinder();
+                _result.n = n;
+                _result.m = m;
+                _result.stpmax = stpmax;
+                _result.modelage = modelage;
+                _result.maxmodelage = maxmodelage;
+                _result.hasfi = hasfi;
+                _result.epsx = epsx;
+                _result.x = (double[])x.Clone();
+                _result.f = f;
+                _result.fi = (double[])fi.Clone();
+                _result.needf = needf;
+                _result.needfi = needfi;
+                _result.fbase = fbase;
+                _result.modeldiag = (double[])modeldiag.Clone();
+                _result.xbase = (double[])xbase.Clone();
+                _result.fibase = (double[])fibase.Clone();
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.havebndl = (bool[])havebndl.Clone();
+                _result.havebndu = (bool[])havebndu.Clone();
+                _result.s = (double[])s.Clone();
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.xdir = (double[])xdir.Clone();
+                _result.choleskybuf = (double[])choleskybuf.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.tmpct = (int[])tmpct.Clone();
+                _result.actualdecrease = actualdecrease;
+                _result.predicteddecrease = predicteddecrease;
+                _result.qpstate = (minqp.minqpstate)qpstate.make_copy();
+                _result.qprep = (minqp.minqpreport)qprep.make_copy();
+                _result.tmpsp = (sparse.sparsematrix)tmpsp.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        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 : apobject
+        {
+            public int n;
+            public int m;
+            public double diffstep;
+            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 bool userterminationneeded;
+            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[,] cleic;
+            public int nec;
+            public int nic;
+            public double lambdav;
+            public double nu;
+            public int modelage;
+            public double[] xnew;
+            public double[] xdir;
+            public double[] deltax;
+            public double[] deltaf;
+            public bool deltaxready;
+            public bool deltafready;
+            public optserv.smoothnessmonitor smonitor;
+            public double teststep;
+            public double[] lastscaleused;
+            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 double[] fc1;
+            public double[] gm1;
+            public double[] gp1;
+            public double[] gc1;
+            public minlbfgs.minlbfgsstate internalstate;
+            public minlbfgs.minlbfgsreport internalrep;
+            public minqp.minqpstate qpstate;
+            public minqp.minqpreport qprep;
+            public minlmstepfinder finderstate;
+            public minlmstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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];
+                cleic = new double[0,0];
+                xnew = new double[0];
+                xdir = new double[0];
+                deltax = new double[0];
+                deltaf = new double[0];
+                smonitor = new optserv.smoothnessmonitor();
+                lastscaleused = new double[0];
+                rstate = new rcommstate();
+                choleskybuf = new double[0];
+                tmp0 = new double[0];
+                fm1 = new double[0];
+                fp1 = new double[0];
+                fc1 = new double[0];
+                gm1 = new double[0];
+                gp1 = new double[0];
+                gc1 = new double[0];
+                internalstate = new minlbfgs.minlbfgsstate();
+                internalrep = new minlbfgs.minlbfgsreport();
+                qpstate = new minqp.minqpstate();
+                qprep = new minqp.minqpreport();
+                finderstate = new minlmstepfinder();
+            }
+            public override alglib.apobject make_copy()
+            {
+                minlmstate _result = new minlmstate();
+                _result.n = n;
+                _result.m = m;
+                _result.diffstep = diffstep;
+                _result.epsx = epsx;
+                _result.maxits = maxits;
+                _result.xrep = xrep;
+                _result.stpmax = stpmax;
+                _result.maxmodelage = maxmodelage;
+                _result.makeadditers = makeadditers;
+                _result.x = (double[])x.Clone();
+                _result.f = f;
+                _result.fi = (double[])fi.Clone();
+                _result.j = (double[,])j.Clone();
+                _result.h = (double[,])h.Clone();
+                _result.g = (double[])g.Clone();
+                _result.needf = needf;
+                _result.needfg = needfg;
+                _result.needfgh = needfgh;
+                _result.needfij = needfij;
+                _result.needfi = needfi;
+                _result.xupdated = xupdated;
+                _result.userterminationneeded = userterminationneeded;
+                _result.algomode = algomode;
+                _result.hasf = hasf;
+                _result.hasfi = hasfi;
+                _result.hasg = hasg;
+                _result.xbase = (double[])xbase.Clone();
+                _result.fbase = fbase;
+                _result.fibase = (double[])fibase.Clone();
+                _result.gbase = (double[])gbase.Clone();
+                _result.quadraticmodel = (double[,])quadraticmodel.Clone();
+                _result.bndl = (double[])bndl.Clone();
+                _result.bndu = (double[])bndu.Clone();
+                _result.havebndl = (bool[])havebndl.Clone();
+                _result.havebndu = (bool[])havebndu.Clone();
+                _result.s = (double[])s.Clone();
+                _result.cleic = (double[,])cleic.Clone();
+                _result.nec = nec;
+                _result.nic = nic;
+                _result.lambdav = lambdav;
+                _result.nu = nu;
+                _result.modelage = modelage;
+                _result.xnew = (double[])xnew.Clone();
+                _result.xdir = (double[])xdir.Clone();
+                _result.deltax = (double[])deltax.Clone();
+                _result.deltaf = (double[])deltaf.Clone();
+                _result.deltaxready = deltaxready;
+                _result.deltafready = deltafready;
+                _result.smonitor = (optserv.smoothnessmonitor)smonitor.make_copy();
+                _result.teststep = teststep;
+                _result.lastscaleused = (double[])lastscaleused.Clone();
+                _result.repiterationscount = repiterationscount;
+                _result.repterminationtype = repterminationtype;
+                _result.repnfunc = repnfunc;
+                _result.repnjac = repnjac;
+                _result.repngrad = repngrad;
+                _result.repnhess = repnhess;
+                _result.repncholesky = repncholesky;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.choleskybuf = (double[])choleskybuf.Clone();
+                _result.tmp0 = (double[])tmp0.Clone();
+                _result.actualdecrease = actualdecrease;
+                _result.predicteddecrease = predicteddecrease;
+                _result.xm1 = xm1;
+                _result.xp1 = xp1;
+                _result.fm1 = (double[])fm1.Clone();
+                _result.fp1 = (double[])fp1.Clone();
+                _result.fc1 = (double[])fc1.Clone();
+                _result.gm1 = (double[])gm1.Clone();
+                _result.gp1 = (double[])gp1.Clone();
+                _result.gc1 = (double[])gc1.Clone();
+                _result.internalstate = (minlbfgs.minlbfgsstate)internalstate.make_copy();
+                _result.internalrep = (minlbfgs.minlbfgsreport)internalrep.make_copy();
+                _result.qpstate = (minqp.minqpstate)qpstate.make_copy();
+                _result.qprep = (minqp.minqpreport)qprep.make_copy();
+                _result.finderstate = (minlmstepfinder)finderstate.make_copy();
+                return _result;
+            }
+        };
+
+
+        /*************************************************************************
+        Optimization report, filled by MinLMResults() function
+
+        FIELDS:
+        * TerminationType, completetion code:
+            * -8    optimizer detected NAN/INF values either in the function itself,
+                    or in its Jacobian
+            * -5    inappropriate solver was used:
+                    * solver created with minlmcreatefgh() used  on  problem  with
+                      general linear constraints (set with minlmsetlc() call).
+            * -3    constraints are inconsistent
+            *  2    relative step is no more than EpsX.
+            *  5    MaxIts steps was taken
+            *  7    stopping conditions are too stringent,
+                    further improvement is impossible
+            *  8    terminated   by  user  who  called  MinLMRequestTermination().
+                    X contains point which was "current accepted" when termination
+                    request was submitted.
+        * 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 : apobject
+        {
+            public int iterationscount;
+            public int terminationtype;
+            public int nfunc;
+            public int njac;
+            public int ngrad;
+            public int nhess;
+            public int ncholesky;
+            public minlmreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                minlmreport _result = new minlmreport();
+                _result.iterationscount = iterationscount;
+                _result.terminationtype = terminationtype;
+                _result.nfunc = nfunc;
+                _result.njac = njac;
+                _result.ngrad = ngrad;
+                _result.nhess = nhess;
+                _result.ncholesky = ncholesky;
+                return _result;
+            }
+        };
+
+
+
+
+        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.xparams _params)
+        {
+            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, _params), "MinLMCreateVJ: X contains infinite or NaN values!");
+            
+            //
+            // initialize, check parameters
+            //
+            state.teststep = 0;
+            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, _params);
+            minlmsetacctype(state, 0, _params);
+            minlmsetcond(state, 0, 0, _params);
+            minlmsetxrep(state, false, _params);
+            minlmsetstpmax(state, 0, _params);
+            minlmrestartfrom(state, x, _params);
+        }
+
+
+        /*************************************************************************
+                        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.xparams _params)
+        {
+            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, _params), "MinLMCreateV: X contains infinite or NaN values!");
+            
+            //
+            // Initialize
+            //
+            state.teststep = 0;
+            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, _params);
+            minlmsetacctype(state, 1, _params);
+            minlmsetcond(state, 0, 0, _params);
+            minlmsetxrep(state, false, _params);
+            minlmsetstpmax(state, 0, _params);
+            minlmrestartfrom(state, x, _params);
+        }
+
+
+        /*************************************************************************
+            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.xparams _params)
+        {
+            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, _params), "MinLMCreateFGH: X contains infinite or NaN values!");
+            
+            //
+            // initialize
+            //
+            state.teststep = 0;
+            state.n = n;
+            state.m = 0;
+            state.algomode = 2;
+            state.hasf = true;
+            state.hasfi = false;
+            state.hasg = true;
+            
+            //
+            // init2
+            //
+            lmprepare(n, 0, true, state, _params);
+            minlmsetacctype(state, 2, _params);
+            minlmsetcond(state, 0, 0, _params);
+            minlmsetxrep(state, false, _params);
+            minlmsetstpmax(state, 0, _params);
+            minlmrestartfrom(state, x, _params);
+        }
+
+
+        /*************************************************************************
+        This function sets stopping conditions for Levenberg-Marquardt optimization
+        algorithm.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+            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()
+                        Recommended values: 1E-9 ... 1E-12.
+            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  EpsX=0  and  MaxIts=0  (simultaneously)  will  lead  to automatic
+        stopping criterion selection (small EpsX).
+
+        NOTE: it is not recommended to set large EpsX (say, 0.001). Because LM  is
+              a second-order method, it performs very precise steps anyway.
+
+          -- ALGLIB --
+             Copyright 02.04.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmsetcond(minlmstate state,
+            double epsx,
+            int maxits,
+            alglib.xparams _params)
+        {
+            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)(epsx)==(double)(0) && maxits==0 )
+            {
+                epsx = 1.0E-9;
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 sets general linear constraints for LM optimizer
+
+        Linear constraints are inactive by default (after initial creation).  They
+        are preserved until explicitly turned off with another minlmsetlc() call.
+
+        INPUT PARAMETERS:
+            State   -   structure stores algorithm state
+            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
+
+        IMPORTANT: if you have linear constraints, it is strongly  recommended  to
+                   set scale of variables with minlmsetscale(). QP solver which is
+                   used to calculate linearly constrained steps heavily relies  on
+                   good scaling of input problems.
+
+        IMPORTANT: solvers created with minlmcreatefgh()  do  not  support  linear
+                   constraints.
+                   
+        NOTE: linear  (non-bound)  constraints are satisfied only approximately  -
+              there  always  exists some violation due  to  numerical  errors  and
+              algorithmic limitations.
+
+        NOTE: general linear constraints  add  significant  overhead  to  solution
+              process. Although solver performs roughly same amount of  iterations
+              (when compared  with  similar  box-only  constrained  problem), each
+              iteration   now    involves  solution  of  linearly  constrained  QP
+              subproblem, which requires ~3-5 times more Cholesky  decompositions.
+              Thus, if you can reformulate your problem in such way  this  it  has
+              only box constraints, it may be beneficial to do so.
+
+          -- ALGLIB --
+             Copyright 14.01.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmsetlc(minlmstate state,
+            double[,] c,
+            int[] ct,
+            int k,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+            int i_ = 0;
+
+            n = state.n;
+            
+            //
+            // First, check for errors in the inputs
+            //
+            alglib.ap.assert(k>=0, "MinLMSetLC: K<0");
+            alglib.ap.assert(alglib.ap.cols(c)>=n+1 || k==0, "MinLMSetLC: Cols(C)<N+1");
+            alglib.ap.assert(alglib.ap.rows(c)>=k, "MinLMSetLC: Rows(C)<K");
+            alglib.ap.assert(alglib.ap.len(ct)>=k, "MinLMSetLC: Length(CT)<K");
+            alglib.ap.assert(apserv.apservisfinitematrix(c, k, n+1, _params), "MinLMSetLC: C contains infinite or NaN values!");
+            
+            //
+            // Handle zero K
+            //
+            if( k==0 )
+            {
+                state.nec = 0;
+                state.nic = 0;
+                return;
+            }
+            
+            //
+            // Equality constraints are stored first, in the upper
+            // NEC rows of State.CLEIC matrix. Inequality constraints
+            // are stored in the next NIC rows.
+            //
+            // NOTE: we convert inequality constraints to the form
+            // A*x<=b before copying them.
+            //
+            apserv.rmatrixsetlengthatleast(ref state.cleic, k, n+1, _params);
+            state.nec = 0;
+            state.nic = 0;
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]==0 )
+                {
+                    for(i_=0; i_<=n;i_++)
+                    {
+                        state.cleic[state.nec,i_] = c[i,i_];
+                    }
+                    state.nec = state.nec+1;
+                }
+            }
+            for(i=0; i<=k-1; i++)
+            {
+                if( ct[i]!=0 )
+                {
+                    if( ct[i]>0 )
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec+state.nic,i_] = -c[i,i_];
+                        }
+                    }
+                    else
+                    {
+                        for(i_=0; i_<=n;i_++)
+                        {
+                            state.cleic[state.nec+state.nic,i_] = c[i,i_];
+                        }
+                    }
+                    state.nic = state.nic+1;
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        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 prohibitively
+        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         +
+
+        DEFAULT 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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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;
+            double fnew = 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];
+                fnew = state.rstate.ra[3];
+            }
+            else
+            {
+                n = 359;
+                m = -58;
+                iflag = -919;
+                i = -909;
+                k = 81;
+                bflag = true;
+                v = 74;
+                s = -788;
+                t = 809;
+                fnew = 205;
+            }
+            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;
+            }
+            if( state.rstate.stage==17 )
+            {
+                goto lbl_17;
+            }
+            if( state.rstate.stage==18 )
+            {
+                goto lbl_18;
+            }
+            if( state.rstate.stage==19 )
+            {
+                goto lbl_19;
+            }
+            if( state.rstate.stage==20 )
+            {
+                goto lbl_20;
+            }
+            if( state.rstate.stage==21 )
+            {
+                goto lbl_21;
+            }
+            if( state.rstate.stage==22 )
+            {
+                goto lbl_22;
+            }
+            if( state.rstate.stage==23 )
+            {
+                goto lbl_23;
+            }
+            if( state.rstate.stage==24 )
+            {
+                goto lbl_24;
+            }
+            if( state.rstate.stage==25 )
+            {
+                goto lbl_25;
+            }
+            if( state.rstate.stage==26 )
+            {
+                goto lbl_26;
+            }
+            if( state.rstate.stage==27 )
+            {
+                goto lbl_27;
+            }
+            
+            //
+            // 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;
+            state.userterminationneeded = false;
+            if( m>0 )
+            {
+                optserv.smoothnessmonitorinit(state.smonitor, n, m, false, _params);
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.lastscaleused[i] = state.s[i];
+            }
+            
+            //
+            // Prepare LM step finder and enforce/check feasibility of constraints
+            //
+            if( !minlmstepfinderinit(state.finderstate, n, m, state.maxmodelage, state.hasfi, ref state.xbase, state.bndl, state.bndu, state.cleic, state.nec, state.nic, state.s, state.stpmax, state.epsx, _params) )
+            {
+                state.repterminationtype = -3;
+                result = false;
+                return result;
+            }
+            
+            //
+            // set constraints for obsolete QP solver
+            //
+            minqp.minqpsetbc(state.qpstate, state.bndl, state.bndu, _params);
+            
+            //
+            //  Check correctness of the analytic Jacobian
+            //
+            clearrequestfields(state, _params);
+            if( !(state.algomode==1 && (double)(state.teststep)>(double)(0)) )
+            {
+                goto lbl_28;
+            }
+            alglib.ap.assert(m>0, "MinLM: integrity check failed");
+        lbl_30:
+            if( !optserv.smoothnessmonitorcheckgradientatx0(state.smonitor, state.xbase, state.s, state.bndl, state.bndu, true, state.teststep, _params) )
+            {
+                goto lbl_31;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.x[i] = state.smonitor.x[i];
+            }
+            state.needfij = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfij = false;
+            for(i=0; i<=m-1; i++)
+            {
+                state.smonitor.fi[i] = state.fi[i];
+                for(k=0; k<=n-1; k++)
+                {
+                    state.smonitor.j[i,k] = state.j[i,k];
+                }
+            }
+            goto lbl_30;
+        lbl_31:
+        lbl_28:
+            
+            //
+            // 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_32;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state, _params);
+            if( !state.hasf )
+            {
+                goto lbl_34;
+            }
+            state.needf = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.needf = false;
+            goto lbl_35;
+        lbl_34:
+            alglib.ap.assert(state.hasfi, "MinLM: internal error 2!");
+            state.needfi = true;
+            state.rstate.stage = 2;
+            goto lbl_rcomm;
+        lbl_2:
+            state.needfi = false;
+            v = 0.0;
+            for(i_=0; i_<=m-1;i_++)
+            {
+                v += state.fi[i_]*state.fi[i_];
+            }
+            state.f = v;
+        lbl_35:
+            state.repnfunc = state.repnfunc+1;
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 3;
+            goto lbl_rcomm;
+        lbl_3:
+            state.xupdated = false;
+        lbl_32:
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.x[i_] = state.xbase[i_];
+                }
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            
+            //
+            // Prepare control variables
+            //
+            state.nu = 1;
+            state.lambdav = -math.maxrealnumber;
+            state.modelage = state.maxmodelage+1;
+            state.deltaxready = false;
+            state.deltafready = false;
+            if( state.algomode==2 )
+            {
+                goto lbl_36;
+            }
+            
+            //
+            // Jacobian-based optimization mode
+            //
+            // 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_38:
+            if( false )
+            {
+                goto lbl_39;
+            }
+            
+            //
+            // 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.
+            //
+            alglib.ap.assert(state.algomode==0 || state.algomode==1, "MinLM: integrity check failed");
+            if( !(state.modelage>state.maxmodelage || !(state.deltaxready && state.deltafready)) )
+            {
+                goto lbl_40;
+            }
+            
+            //
+            // Refresh model (using either finite differences or analytic Jacobian)
+            //
+            if( state.algomode!=0 )
+            {
+                goto lbl_42;
+            }
+            
+            //
+            // 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_44:
+            if( k>n-1 )
+            {
+                goto lbl_46;
+            }
+            
+            //
+            // 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, _params);
+            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.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, _params);
+            state.needfi = true;
+            state.rstate.stage = 5;
+            goto lbl_rcomm;
+        lbl_5:
+            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_44;
+        lbl_46:
+            
+            //
+            // Calculate F(XBase)
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state, _params);
+            state.needfi = true;
+            state.rstate.stage = 6;
+            goto lbl_rcomm;
+        lbl_6:
+            state.needfi = false;
+            state.repnfunc = state.repnfunc+1;
+            state.repnjac = state.repnjac+1;
+            
+            //
+            // New model
+            //
+            state.modelage = 0;
+            goto lbl_43;
+        lbl_42:
+            
+            //
+            // Obtain f[] and Jacobian
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state, _params);
+            state.needfij = true;
+            state.rstate.stage = 7;
+            goto lbl_rcomm;
+        lbl_7:
+            state.needfij = false;
+            state.repnfunc = state.repnfunc+1;
+            state.repnjac = state.repnjac+1;
+            
+            //
+            // New model
+            //
+            state.modelage = 0;
+        lbl_43:
+            goto lbl_41;
+        lbl_40:
+            
+            //
+            // 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_41:
+            ablas.rmatrixgemm(n, n, m, 2.0, state.j, 0, 0, 1, state.j, 0, 0, 0, 0.0, state.quadraticmodel, 0, 0, _params);
+            ablas.rmatrixmv(n, m, state.j, 0, 0, 1, state.fi, 0, state.gbase, 0, _params);
+            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_];
+            }
+            state.deltaxready = false;
+            state.deltafready = false;
+            
+            //
+            // Perform integrity check (presense of NAN/INF)
+            //
+            v = state.fbase;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.1*v+state.gbase[i];
+            }
+            if( !math.isfinite(v) )
+            {
+                
+                //
+                // Break!
+                //
+                state.repterminationtype = -8;
+                result = false;
+                return result;
+            }
+            
+            //
+            // 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;
+                }
+            }
+            
+            //
+            // 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:
+            // * -8, if internal integrity control detected NAN/INF in function values
+            // * -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
+            // * >0, successful termination, step is less than EpsX
+            //
+            // State.Nu can have any value on enter, but after exit it is set to 1.0
+            //
+            iflag = -99;
+            minlmstepfinderstart(state.finderstate, state.quadraticmodel, state.gbase, state.fbase, state.xbase, state.fibase, state.modelage, _params);
+        lbl_47:
+            if( !minlmstepfinderiteration(state.finderstate, ref state.lambdav, ref state.nu, ref state.xnew, state.deltax, ref state.deltaxready, state.deltaf, ref state.deltafready, ref iflag, ref fnew, ref state.repncholesky, _params) )
+            {
+                goto lbl_48;
+            }
+            alglib.ap.assert(state.hasfi || state.hasf, "MinLM: internal error 2!");
+            state.repnfunc = state.repnfunc+1;
+            clearrequestfields(state, _params);
+            if( !state.finderstate.needfi )
+            {
+                goto lbl_49;
+            }
+            alglib.ap.assert(state.hasfi, "MinLM: internal error 2!");
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.finderstate.x[i_];
+            }
+            state.needfi = true;
+            state.rstate.stage = 8;
+            goto lbl_rcomm;
+        lbl_8:
+            state.needfi = false;
+            for(i_=0; i_<=m-1;i_++)
+            {
+                state.finderstate.fi[i_] = state.fi[i_];
+            }
+            goto lbl_47;
+        lbl_49:
+            if( !state.finderstate.needf )
+            {
+                goto lbl_51;
+            }
+            alglib.ap.assert(state.hasf, "MinLM: internal error 2!");
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.finderstate.x[i_];
+            }
+            state.needf = true;
+            state.rstate.stage = 9;
+            goto lbl_rcomm;
+        lbl_9:
+            state.needf = false;
+            state.finderstate.f = state.f;
+            goto lbl_47;
+        lbl_51:
+            alglib.ap.assert(false, "MinLM: internal error 2!");
+            goto lbl_47;
+        lbl_48:
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.x[i_] = state.xbase[i_];
+                }
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            state.nu = 1;
+            alglib.ap.assert(((iflag>=-3 && iflag<=0) || iflag==-8) || 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_38;
+            }
+            if( iflag!=-1 )
+            {
+                goto lbl_53;
+            }
+            
+            //
+            // Stopping conditions are too stringent
+            //
+            state.repterminationtype = 7;
+            if( !state.xrep )
+            {
+                goto lbl_55;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.f = state.fbase;
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 10;
+            goto lbl_rcomm;
+        lbl_10:
+            state.xupdated = false;
+        lbl_55:
+            result = false;
+            return result;
+        lbl_53:
+            if( !(iflag==-8 || iflag>0) )
+            {
+                goto lbl_57;
+            }
+            
+            //
+            // Either:
+            // * Integrity check failed - infinities or NANs
+            // * successful termination (step size is small enough)
+            //
+            state.repterminationtype = iflag;
+            if( !state.xrep )
+            {
+                goto lbl_59;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.f = state.fbase;
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 11;
+            goto lbl_rcomm;
+        lbl_11:
+            state.xupdated = false;
+        lbl_59:
+            result = false;
+            return result;
+        lbl_57:
+            state.f = fnew;
+            
+            //
+            // 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");
+            iflag = checkdecrease(state.quadraticmodel, state.gbase, state.fbase, n, state.deltax, state.f, ref state.lambdav, ref state.nu, _params);
+            if( iflag==0 )
+            {
+                goto lbl_61;
+            }
+            state.repterminationtype = iflag;
+            if( !state.xrep )
+            {
+                goto lbl_63;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.f = state.fbase;
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 12;
+            goto lbl_rcomm;
+        lbl_12:
+            state.xupdated = false;
+        lbl_63:
+            result = false;
+            return result;
+        lbl_61:
+            
+            //
+            // 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.xnew[i_];
+            }
+            if( !state.xrep )
+            {
+                goto lbl_65;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 13;
+            goto lbl_rcomm;
+        lbl_13:
+            state.xupdated = false;
+        lbl_65:
+            state.repiterationscount = state.repiterationscount+1;
+            if( state.repiterationscount>=state.maxits && state.maxits>0 )
+            {
+                state.repterminationtype = 5;
+            }
+            if( state.repterminationtype<=0 )
+            {
+                goto lbl_67;
+            }
+            if( !state.xrep )
+            {
+                goto lbl_69;
+            }
+            
+            //
+            // 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, _params);
+            state.xupdated = true;
+            state.rstate.stage = 14;
+            goto lbl_rcomm;
+        lbl_14:
+            state.xupdated = false;
+        lbl_69:
+            result = false;
+            return result;
+        lbl_67:
+            state.modelage = state.modelage+1;
+            goto lbl_38;
+        lbl_39:
+            
+            //
+            // Lambda is too large, we have to break iterations.
+            //
+            state.repterminationtype = 7;
+            if( !state.xrep )
+            {
+                goto lbl_71;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.f = state.fbase;
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 15;
+            goto lbl_rcomm;
+        lbl_15:
+            state.xupdated = false;
+        lbl_71:
+            goto lbl_37;
+        lbl_36:
+            
+            //
+            // Legacy Hessian-based mode
+            //
+            // 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
+            //
+            //
+            if( state.nec+state.nic>0 )
+            {
+                
+                //
+                // FGH solver does not support general linear constraints
+                //
+                state.repterminationtype = -5;
+                result = false;
+                return result;
+            }
+        lbl_73:
+            if( false )
+            {
+                goto lbl_74;
+            }
+            
+            //
+            // 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_75;
+            }
+            
+            //
+            // Calculate f[] and Jacobian
+            //
+            if( !(state.modelage>state.maxmodelage || !(state.deltaxready && state.deltafready)) )
+            {
+                goto lbl_77;
+            }
+            
+            //
+            // Refresh model (using either finite differences or analytic Jacobian)
+            //
+            if( state.algomode!=0 )
+            {
+                goto lbl_79;
+            }
+            
+            //
+            // 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_81:
+            if( k>n-1 )
+            {
+                goto lbl_83;
+            }
+            
+            //
+            // 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, _params);
+            state.needfi = true;
+            state.rstate.stage = 16;
+            goto lbl_rcomm;
+        lbl_16:
+            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, _params);
+            state.needfi = true;
+            state.rstate.stage = 17;
+            goto lbl_rcomm;
+        lbl_17:
+            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_81;
+        lbl_83:
+            
+            //
+            // Calculate F(XBase)
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state, _params);
+            state.needfi = true;
+            state.rstate.stage = 18;
+            goto lbl_rcomm;
+        lbl_18:
+            state.needfi = false;
+            state.repnfunc = state.repnfunc+1;
+            state.repnjac = state.repnjac+1;
+            
+            //
+            // New model
+            //
+            state.modelage = 0;
+            goto lbl_80;
+        lbl_79:
+            
+            //
+            // Obtain f[] and Jacobian
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state, _params);
+            state.needfij = true;
+            state.rstate.stage = 19;
+            goto lbl_rcomm;
+        lbl_19:
+            state.needfij = false;
+            state.repnfunc = state.repnfunc+1;
+            state.repnjac = state.repnjac+1;
+            
+            //
+            // New model
+            //
+            state.modelage = 0;
+        lbl_80:
+            goto lbl_78;
+        lbl_77:
+            
+            //
+            // 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_78:
+            
+            //
+            // 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, state.quadraticmodel, 0, 0, _params);
+            ablas.rmatrixmv(n, m, state.j, 0, 0, 1, state.fi, 0, state.gbase, 0, _params);
+            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_75:
+            if( state.algomode!=2 )
+            {
+                goto lbl_84;
+            }
+            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, _params);
+            state.needfgh = true;
+            state.rstate.stage = 20;
+            goto lbl_rcomm;
+        lbl_20:
+            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, state.quadraticmodel, 0, 0, _params);
+            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_84:
+            alglib.ap.assert(bflag, "MinLM: internal integrity check failed!");
+            state.deltaxready = false;
+            state.deltafready = false;
+            
+            //
+            // Perform integrity check (presense of NAN/INF)
+            //
+            v = state.fbase;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.1*v+state.gbase[i];
+            }
+            if( !math.isfinite(v) )
+            {
+                
+                //
+                // Break!
+                //
+                state.repterminationtype = -8;
+                result = false;
+                return result;
+            }
+            
+            //
+            // 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;
+                }
+            }
+            
+            //
+            // 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_86:
+            if( false )
+            {
+                goto lbl_87;
+            }
+            
+            //
+            // Do we need model update?
+            //
+            if( state.modelage>0 && (double)(state.nu)>=(double)(suspiciousnu) )
+            {
+                iflag = -2;
+                goto lbl_87;
+            }
+            
+            //
+            // 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, _params);
+            minqp.minqpsetoriginfast(state.qpstate, state.xbase, _params);
+            minqp.minqpsetlineartermfast(state.qpstate, state.gbase, _params);
+            minqp.minqpsetquadratictermfast(state.qpstate, state.quadraticmodel, true, 0.0, _params);
+            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, _params);
+            minqp.minqpoptimize(state.qpstate, _params);
+            minqp.minqpresultsbuf(state.qpstate, ref state.xdir, state.qprep, _params);
+            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==-4) || state.qprep.terminationtype==-5, "MinLM: unexpected completion code from QP solver");
+                if( state.qprep.terminationtype==-3 )
+                {
+                    iflag = -3;
+                    goto lbl_87;
+                }
+                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, _params) )
+                {
+                    iflag = -1;
+                    goto lbl_87;
+                }
+                goto lbl_86;
+            }
+            
+            //
+            // 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_88;
+            }
+            if( state.modelage!=0 )
+            {
+                goto lbl_90;
+            }
+            
+            //
+            // Step is too short, model is fresh and we can rely on it.
+            // Terminating.
+            //
+            state.repterminationtype = 2;
+            if( !state.xrep )
+            {
+                goto lbl_92;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.f = state.fbase;
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 21;
+            goto lbl_rcomm;
+        lbl_21:
+            state.xupdated = false;
+        lbl_92:
+            result = false;
+            return result;
+            goto lbl_91;
+        lbl_90:
+            
+            //
+            // Step is suspiciously short, but model is not fresh
+            // and we can't rely on it.
+            //
+            iflag = -2;
+            goto lbl_87;
+        lbl_91:
+        lbl_88:
+            
+            //
+            // 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, _params);
+            if( !state.hasfi )
+            {
+                goto lbl_94;
+            }
+            state.needfi = true;
+            state.rstate.stage = 22;
+            goto lbl_rcomm;
+        lbl_22:
+            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_95;
+        lbl_94:
+            state.needf = true;
+            state.rstate.stage = 23;
+            goto lbl_rcomm;
+        lbl_23:
+            state.needf = false;
+        lbl_95:
+            state.repnfunc = state.repnfunc+1;
+            if( !math.isfinite(state.f) )
+            {
+                
+                //
+                // Integrity check failed, break!
+                //
+                state.repterminationtype = -8;
+                result = false;
+                return result;
+            }
+            if( (double)(state.f)>=(double)(state.fbase) )
+            {
+                
+                //
+                // Increase lambda and continue
+                //
+                if( !increaselambda(ref state.lambdav, ref state.nu, _params) )
+                {
+                    iflag = -1;
+                    goto lbl_87;
+                }
+                goto lbl_86;
+            }
+            
+            //
+            // We've found our step!
+            //
+            iflag = 0;
+            goto lbl_87;
+            goto lbl_86;
+        lbl_87:
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    state.x[i_] = state.xbase[i_];
+                }
+                state.repterminationtype = 8;
+                result = false;
+                return result;
+            }
+            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_73;
+            }
+            if( iflag==-1 )
+            {
+                goto lbl_74;
+            }
+            
+            //
+            // 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_74;
+            }
+            v = state.actualdecrease/state.predicteddecrease;
+            if( (double)(v)>=(double)(0.1) )
+            {
+                goto lbl_96;
+            }
+            if( increaselambda(ref state.lambdav, ref state.nu, _params) )
+            {
+                goto lbl_98;
+            }
+            
+            //
+            // Lambda is too large, we have to break iterations.
+            //
+            state.repterminationtype = 7;
+            if( !state.xrep )
+            {
+                goto lbl_100;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.f = state.fbase;
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 24;
+            goto lbl_rcomm;
+        lbl_24:
+            state.xupdated = false;
+        lbl_100:
+            result = false;
+            return result;
+        lbl_98:
+        lbl_96:
+            if( (double)(v)>(double)(0.5) )
+            {
+                decreaselambda(ref state.lambdav, ref state.nu, _params);
+            }
+            
+            //
+            // 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_102;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 25;
+            goto lbl_rcomm;
+        lbl_25:
+            state.xupdated = false;
+        lbl_102:
+            state.repiterationscount = state.repiterationscount+1;
+            if( state.repiterationscount>=state.maxits && state.maxits>0 )
+            {
+                state.repterminationtype = 5;
+            }
+            if( state.repterminationtype<=0 )
+            {
+                goto lbl_104;
+            }
+            if( !state.xrep )
+            {
+                goto lbl_106;
+            }
+            
+            //
+            // 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, _params);
+            state.xupdated = true;
+            state.rstate.stage = 26;
+            goto lbl_rcomm;
+        lbl_26:
+            state.xupdated = false;
+        lbl_106:
+            result = false;
+            return result;
+        lbl_104:
+            state.modelage = state.modelage+1;
+            goto lbl_73;
+        lbl_74:
+            
+            //
+            // Lambda is too large, we have to break iterations.
+            //
+            state.repterminationtype = 7;
+            if( !state.xrep )
+            {
+                goto lbl_108;
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = state.xbase[i_];
+            }
+            state.f = state.fbase;
+            clearrequestfields(state, _params);
+            state.xupdated = true;
+            state.rstate.stage = 27;
+            goto lbl_rcomm;
+        lbl_27:
+            state.xupdated = false;
+        lbl_108:
+        lbl_37:
+            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;
+            state.rstate.ra[3] = fnew;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This  function  activates/deactivates verification  of  the  user-supplied
+        analytic Jacobian.
+
+        Upon  activation  of  this  option  OptGuard  integrity  checker  performs
+        numerical differentiation of your target function vector  at  the  initial
+        point (note: future versions may also perform check  at  the final  point)
+        and compares numerical Jacobian with analytic one provided by you.
+
+        If difference is too large, an error flag is set and optimization  session
+        continues. After optimization session is over, you can retrieve the report
+        which stores  both  Jacobians,  and  specific  components  highlighted  as
+        suspicious by the OptGuard.
+
+        The OptGuard report can be retrieved with minlmoptguardresults().
+
+        IMPORTANT: gradient check is a high-overhead option which  will  cost  you
+                   about 3*N additional function evaluations. In many cases it may
+                   cost as much as the rest of the optimization session.
+                   
+                   YOU SHOULD NOT USE IT IN THE PRODUCTION CODE UNLESS YOU WANT TO
+                   CHECK DERIVATIVES PROVIDED BY SOME THIRD PARTY.
+
+        NOTE: unlike previous incarnation of the gradient checking code,  OptGuard
+              does NOT interrupt optimization even if it discovers bad gradient.
+              
+        INPUT PARAMETERS:
+            State       -   structure used to store algorithm state
+            TestStep    -   verification step used for numerical differentiation:
+                            * TestStep=0 turns verification off
+                            * TestStep>0 activates verification
+                            You should carefully choose TestStep. Value  which  is
+                            too large (so large that  function  behavior  is  non-
+                            cubic at this scale) will lead  to  false  alarms. Too
+                            short step will result in rounding  errors  dominating
+                            numerical derivative.
+                            
+                            You may use different step for different parameters by
+                            means of setting scale with minlmsetscale().
+
+        === EXPLANATION ==========================================================                    
+
+        In order to verify gradient algorithm performs following steps:
+          * two trial steps are made to X[i]-TestStep*S[i] and X[i]+TestStep*S[i],
+            where X[i] is i-th component of the initial point and S[i] is a  scale
+            of i-th parameter
+          * F(X) is evaluated at these trial points
+          * we perform one more evaluation in the middle point of the interval
+          * we  build  cubic  model using function values and derivatives at trial
+            points and we compare its prediction with actual value in  the  middle
+            point
+
+          -- ALGLIB --
+             Copyright 15.06.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmoptguardgradient(minlmstate state,
+            double teststep,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(math.isfinite(teststep), "MinLMOptGuardGradient: TestStep contains NaN or INF");
+            alglib.ap.assert((double)(teststep)>=(double)(0), "MinLMOptGuardGradient: invalid argument TestStep(TestStep<0)");
+            state.teststep = teststep;
+        }
+
+
+        /*************************************************************************
+        Results of OptGuard integrity check, should be called  after  optimization
+        session is over.
+
+        OptGuard checks analytic Jacobian  against  reference  value  obtained  by
+        numerical differentiation with user-specified step.
+
+        NOTE: other optimizers perform additional OptGuard checks for things  like
+              C0/C1-continuity violations. However, LM optimizer  can  check  only
+              for incorrect Jacobian.
+              
+              The reason is that unlike line search methods LM optimizer does  not
+              perform extensive evaluations along the line. Thus, we simply do not
+              have enough data to catch C0/C1-violations.
+
+        This check is activated with  minlmoptguardgradient() function.
+
+        Following flags are set when these errors are suspected:
+        * rep.badgradsuspected, and additionally:
+          * rep.badgradfidx for specific function (Jacobian row) suspected
+          * rep.badgradvidx for specific variable (Jacobian column) suspected
+          * rep.badgradxbase, a point where gradient/Jacobian is tested
+          * rep.badgraduser, user-provided gradient/Jacobian
+          * rep.badgradnum, reference gradient/Jacobian obtained via numerical
+            differentiation
+
+        INPUT PARAMETERS:
+            state   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            rep     -   OptGuard report
+           
+          -- ALGLIB --
+             Copyright 21.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmoptguardresults(minlmstate state,
+            optguardapi.optguardreport rep,
+            alglib.xparams _params)
+        {
+            optserv.smoothnessmonitorexportreport(state.smonitor, rep, _params);
+        }
+
+
+        /*************************************************************************
+        Levenberg-Marquardt algorithm results
+
+        NOTE: if you activated OptGuard integrity checking functionality and  want
+              to get OptGuard report,  it  can  be  retrieved  with  the  help  of
+              minlmoptguardresults() function.
+
+        INPUT PARAMETERS:
+            State   -   algorithm state
+
+        OUTPUT PARAMETERS:
+            X       -   array[0..N-1], solution
+            Rep     -   optimization  report;  includes  termination   codes   and
+                        additional information. Termination codes are listed below,
+                        see comments for this structure for more info.
+                        Termination code is stored in rep.terminationtype field:
+                        * -8    optimizer detected NAN/INF values either in the
+                                function itself, or in its Jacobian
+                        * -3    constraints are inconsistent
+                        *  2    relative step is no more than EpsX.
+                        *  5    MaxIts steps was taken
+                        *  7    stopping conditions are too stringent,
+                                further improvement is impossible
+                        *  8    terminated by user who called minlmrequesttermination().
+                                X contains point which was "current accepted" when
+                                termination request was submitted.
+
+          -- ALGLIB --
+             Copyright 10.03.2009 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmresults(minlmstate state,
+            ref double[] x,
+            minlmreport rep,
+            alglib.xparams _params)
+        {
+            x = new double[0];
+
+            minlmresultsbuf(state, ref x, rep, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=state.n, "MinLMRestartFrom: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, state.n, _params), "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[3+1];
+            state.rstate.stage = -1;
+            clearrequestfields(state, _params);
+        }
+
+
+        /*************************************************************************
+        This subroutine submits request for termination of running  optimizer.  It
+        should be called from user-supplied callback when user decides that it  is
+        time to "smoothly" terminate optimization process.  As  result,  optimizer
+        stops at point which was "current accepted" when termination  request  was
+        submitted and returns error code 8 (successful termination).
+
+        INPUT PARAMETERS:
+            State   -   optimizer structure
+
+        NOTE: after  request  for  termination  optimizer  may   perform   several
+              additional calls to user-supplied callbacks. It does  NOT  guarantee
+              to stop immediately - it just guarantees that these additional calls
+              will be discarded later.
+
+        NOTE: calling this function on optimizer which is NOT running will have no
+              effect.
+              
+        NOTE: multiple calls to this function are possible. First call is counted,
+              subsequent calls are silently ignored.
+
+          -- ALGLIB --
+             Copyright 08.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void minlmrequesttermination(minlmstate state,
+            alglib.xparams _params)
+        {
+            state.userterminationneeded = true;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            minlmcreatevj(n, m, x, state, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            minlmcreatefj(n, m, x, state, _params);
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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, _params), "MinLMCreateFJ: X contains infinite or NaN values!");
+            
+            //
+            // initialize
+            //
+            state.teststep = 0;
+            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, _params);
+            minlmsetacctype(state, 0, _params);
+            minlmsetcond(state, 0, 0, _params);
+            minlmsetxrep(state, false, _params);
+            minlmsetstpmax(state, 0, _params);
+            minlmrestartfrom(state, x, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            optserv.smoothnessmonitorinit(state.smonitor, 0, 0, false, _params);
+            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];
+                state.fc1 = new double[m];
+                state.gm1 = new double[m];
+                state.gp1 = new double[m];
+                state.gc1 = 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, _params);
+            minlbfgs.minlbfgssetcond(state.internalstate, 0.0, 0.0, 0.0, Math.Min(additers, n), _params);
+            
+            //
+            // Prepare internal QP solver
+            //
+            minqp.minqpcreate(n, state.qpstate, _params);
+            minqp.minqpsetalgoquickqp(state.qpstate, 0.0, 0.0, apserv.coalesce(0.01*state.epsx, 1.0E-12, _params), 10, true, _params);
+            
+            //
+            // 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];
+            state.lastscaleused = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                state.s[i] = 1.0;
+                state.lastscaleused[i] = 1.0;
+            }
+            
+            //
+            // Prepare linear constraints
+            //
+            state.nec = 0;
+            state.nic = 0;
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(minlmstate state,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            nu = 1;
+            if( (double)(Math.Log(lambdav)+Math.Log(lambdadown))<(double)(Math.Log(math.minrealnumber)) )
+            {
+                lambdav = math.minrealnumber;
+            }
+            else
+            {
+                lambdav = lambdav*lambdadown;
+            }
+        }
+
+
+        /*************************************************************************
+        This function compares actual decrease vs predicted decrease  and  updates
+        LambdaV/Nu accordingly.
+
+        INPUT PARAMETERS:
+            QuadraticModel      -   array[N,N], full Hessian matrix of quadratic
+                                    model at deltaX=0
+            GBase               -   array[N], gradient at deltaX=0
+            FBase               -   F(deltaX=0)
+            N                   -   size
+            DeltaX              -   step vector
+            FNew                -   new function value
+            LambdaV             -   lambda-value, updated on exit
+            Nu                  -   Nu-multiplier, updated on exit
+
+        On exit it returns:
+        * Result=0  - if we have to continue iterations
+        * Result<>0 - if termination with completion code Result is requested
+
+          -- ALGLIB --
+             Copyright 17.02.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static int checkdecrease(double[,] quadraticmodel,
+            double[] gbase,
+            double fbase,
+            int n,
+            double[] deltax,
+            double fnew,
+            ref double lambdav,
+            ref double nu,
+            alglib.xparams _params)
+        {
+            int result = 0;
+            int i = 0;
+            double v = 0;
+            double t = 0;
+            double predicteddecrease = 0;
+            double actualdecrease = 0;
+            int i_ = 0;
+
+            result = 0;
+            t = 0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = 0.0;
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    v += quadraticmodel[i,i_]*deltax[i_];
+                }
+                t = t+deltax[i]*gbase[i]+0.5*deltax[i]*v;
+            }
+            predicteddecrease = -t;
+            actualdecrease = -(fnew-fbase);
+            if( (double)(predicteddecrease)<=(double)(0) )
+            {
+                result = 7;
+                return result;
+            }
+            v = actualdecrease/predicteddecrease;
+            if( (double)(v)<(double)(0.1) )
+            {
+                if( !increaselambda(ref lambdav, ref nu, _params) )
+                {
+                    
+                    //
+                    // Lambda is too large, we have to break iterations.
+                    //
+                    result = 7;
+                    return result;
+                }
+            }
+            if( (double)(v)>(double)(0.5) )
+            {
+                decreaselambda(ref lambdav, ref nu, _params);
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This  function  initializes  step finder object  with  problem  statement;
+        model  parameters  specified  during  this  call  should not (and can not)
+        change during object lifetime (although it is  possible  to  re-initialize
+        object with different settings).
+
+        This function reuses internally allocated objects as much as possible.
+
+        In addition to initializing step finder, this function enforces feasibility
+        in initial point X passed to this function. It is important that LM iteration
+        starts from feasible point and performs feasible steps;
+
+        RETURN VALUE:
+            True for successful initialization
+            False for inconsistent constraints; you should not use step finder if
+            it returned False.
+        *************************************************************************/
+        private static bool minlmstepfinderinit(minlmstepfinder state,
+            int n,
+            int m,
+            int maxmodelage,
+            bool hasfi,
+            ref double[] xbase,
+            double[] bndl,
+            double[] bndu,
+            double[,] cleic,
+            int nec,
+            int nic,
+            double[] s,
+            double stpmax,
+            double epsx,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+
+            state.n = n;
+            state.m = m;
+            state.maxmodelage = maxmodelage;
+            state.hasfi = hasfi;
+            state.stpmax = stpmax;
+            state.epsx = epsx;
+            
+            //
+            // Allocate temporaries, create QP solver, select QP algorithm
+            //
+            apserv.rvectorsetlengthatleast(ref state.bndl, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.bndu, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.s, n, _params);
+            apserv.bvectorsetlengthatleast(ref state.havebndl, n, _params);
+            apserv.bvectorsetlengthatleast(ref state.havebndu, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.x, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.xbase, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmp0, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.modeldiag, n, _params);
+            apserv.ivectorsetlengthatleast(ref state.tmpct, nec+nic, _params);
+            apserv.rvectorsetlengthatleast(ref state.xdir, n, _params);
+            if( hasfi )
+            {
+                apserv.rvectorsetlengthatleast(ref state.fi, m, _params);
+                apserv.rvectorsetlengthatleast(ref state.fibase, m, _params);
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                alglib.ap.assert(math.isfinite(bndl[i]) || Double.IsNegativeInfinity(bndl[i]), "MinLM: integrity check failed");
+                alglib.ap.assert(math.isfinite(bndu[i]) || Double.IsPositiveInfinity(bndu[i]), "MinLM: integrity check failed");
+                state.bndl[i] = bndl[i];
+                state.havebndl[i] = math.isfinite(bndl[i]);
+                state.bndu[i] = bndu[i];
+                state.havebndu[i] = math.isfinite(bndu[i]);
+                state.s[i] = s[i];
+            }
+            for(i=0; i<=nec-1; i++)
+            {
+                state.tmpct[i] = 0;
+            }
+            for(i=0; i<=nic-1; i++)
+            {
+                state.tmpct[nec+i] = -1;
+            }
+            minqp.minqpcreate(n, state.qpstate, _params);
+            if( nec+nic==0 )
+            {
+                minqp.minqpsetalgoquickqp(state.qpstate, 0.0, 0.0, apserv.coalesce(0.01*epsx, 1.0E-12, _params), 10, true, _params);
+            }
+            else
+            {
+                minqp.minqpsetalgodenseaul(state.qpstate, apserv.coalesce(0.01*epsx, 1.0E-12, _params), 100, 10, _params);
+            }
+            minqp.minqpsetbc(state.qpstate, bndl, bndu, _params);
+            minqp.minqpsetlc(state.qpstate, cleic, state.tmpct, nec+nic, _params);
+            minqp.minqpsetscale(state.qpstate, s, _params);
+            
+            //
+            // Check feasibility of constraints:
+            // * check/enforce box constraints (straightforward)
+            // * prepare QP subproblem which return us a feasible point
+            //
+            result = true;
+            for(i=0; i<=n-1; i++)
+            {
+                if( (state.havebndl[i] && state.havebndu[i]) && (double)(state.bndl[i])>(double)(state.bndu[i]) )
+                {
+                    result = false;
+                    return result;
+                }
+                if( state.havebndl[i] && (double)(xbase[i])<(double)(state.bndl[i]) )
+                {
+                    xbase[i] = state.bndl[i];
+                }
+                if( state.havebndu[i] && (double)(xbase[i])>(double)(state.bndu[i]) )
+                {
+                    xbase[i] = state.bndu[i];
+                }
+            }
+            if( nec+nic>0 )
+            {
+                
+                //
+                // Well, we have linear constraints... let's use heavy machinery.
+                //
+                // We will modify QP solver state below, but everything will be
+                // restored in MinLMStepFinderStart().
+                //
+                sparse.sparsecreate(n, n, n, state.tmpsp, _params);
+                for(i=0; i<=n-1; i++)
+                {
+                    sparse.sparseset(state.tmpsp, i, i, 0.5, _params);
+                    state.tmp0[i] = 0;
+                }
+                minqp.minqpsetstartingpointfast(state.qpstate, xbase, _params);
+                minqp.minqpsetoriginfast(state.qpstate, xbase, _params);
+                minqp.minqpsetlineartermfast(state.qpstate, state.tmp0, _params);
+                minqp.minqpsetquadratictermsparse(state.qpstate, state.tmpsp, true, _params);
+                minqp.minqpoptimize(state.qpstate, _params);
+                minqp.minqpresultsbuf(state.qpstate, ref xbase, state.qprep, _params);
+            }
+            return result;
+        }
+
+
+        /*************************************************************************
+        This function prepares LM step search session.
+        *************************************************************************/
+        private static void minlmstepfinderstart(minlmstepfinder state,
+            double[,] quadraticmodel,
+            double[] gbase,
+            double fbase,
+            double[] xbase,
+            double[] fibase,
+            int modelage,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int n = 0;
+
+            n = state.n;
+            state.rstate.ia = new int[2+1];
+            state.rstate.ba = new bool[0+1];
+            state.rstate.ra = new double[0+1];
+            state.rstate.stage = -1;
+            state.modelage = modelage;
+            state.fbase = fbase;
+            if( state.hasfi )
+            {
+                for(i=0; i<=state.m-1; i++)
+                {
+                    state.fibase[i] = fibase[i];
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.xbase[i] = xbase[i];
+                state.modeldiag[i] = quadraticmodel[i,i];
+            }
+            minqp.minqpsetstartingpointfast(state.qpstate, xbase, _params);
+            minqp.minqpsetoriginfast(state.qpstate, xbase, _params);
+            minqp.minqpsetlineartermfast(state.qpstate, gbase, _params);
+            minqp.minqpsetquadratictermfast(state.qpstate, quadraticmodel, true, 0.0, _params);
+        }
+
+
+        /*************************************************************************
+        This function runs LM step search session.
+        //
+        // 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:
+        // * -8, if infinities/NANs were detected in function values/gradient
+        // * -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
+        // * >0  - successful completion (step size is small enough)
+        //
+        // State.Nu can have any value on enter, but after exit it is set to 1.0
+        //
+        *************************************************************************/
+        private static bool minlmstepfinderiteration(minlmstepfinder state,
+            ref double lambdav,
+            ref double nu,
+            ref double[] xnew,
+            double[] deltax,
+            ref bool deltaxready,
+            double[] deltaf,
+            ref bool deltafready,
+            ref int iflag,
+            ref double fnew,
+            ref int ncholesky,
+            alglib.xparams _params)
+        {
+            bool result = new bool();
+            int i = 0;
+            bool bflag = new bool();
+            double v = 0;
+            int n = 0;
+            int m = 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];
+                n = state.rstate.ia[1];
+                m = state.rstate.ia[2];
+                bflag = state.rstate.ba[0];
+                v = state.rstate.ra[0];
+            }
+            else
+            {
+                i = -838;
+                n = 939;
+                m = -526;
+                bflag = true;
+                v = -541;
+            }
+            if( state.rstate.stage==0 )
+            {
+                goto lbl_0;
+            }
+            if( state.rstate.stage==1 )
+            {
+                goto lbl_1;
+            }
+            
+            //
+            // Routine body
+            //
+            iflag = -99;
+            n = state.n;
+            m = state.m;
+        lbl_2:
+            if( false )
+            {
+                goto lbl_3;
+            }
+            deltaxready = false;
+            deltafready = false;
+            
+            //
+            // Do we need model update?
+            //
+            if( state.modelage>0 && (double)(nu)>=(double)(suspiciousnu) )
+            {
+                iflag = -2;
+                goto lbl_3;
+            }
+            
+            //
+            // 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;
+            for(i=0; i<=n-1; i++)
+            {
+                state.tmp0[i] = state.modeldiag[i]+lambdav/math.sqr(state.s[i]);
+            }
+            minqp.minqprewritediagonal(state.qpstate, state.tmp0, _params);
+            minqp.minqpoptimize(state.qpstate, _params);
+            minqp.minqpresultsbuf(state.qpstate, ref xnew, state.qprep, _params);
+            ncholesky = ncholesky+state.qprep.ncholesky;
+            if( state.qprep.terminationtype==-3 )
+            {
+                
+                //
+                // Infeasible constraints
+                //
+                iflag = -3;
+                goto lbl_3;
+            }
+            if( state.qprep.terminationtype==-4 || state.qprep.terminationtype==-5 )
+            {
+                
+                //
+                // Unconstrained direction of negative curvature was detected
+                //
+                if( !increaselambda(ref lambdav, ref nu, _params) )
+                {
+                    iflag = -1;
+                    goto lbl_3;
+                }
+                goto lbl_2;
+            }
+            alglib.ap.assert(state.qprep.terminationtype>0, "MinLM: unexpected completion code from QP solver");
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.xdir[i_] = xnew[i_];
+            }
+            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 = v+math.sqr(state.xdir[i]/state.s[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;
+            }
+            if( !bflag )
+            {
+                
+                //
+                // Solution failed:
+                // try to increase lambda to make matrix positive definite and continue.
+                //
+                if( !increaselambda(ref lambdav, ref nu, _params) )
+                {
+                    iflag = -1;
+                    goto lbl_3;
+                }
+                goto lbl_2;
+            }
+            
+            //
+            // 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_++)
+            {
+                deltax[i_] = xnew[i_];
+            }
+            for(i_=0; i_<=n-1;i_++)
+            {
+                deltax[i_] = deltax[i_] - state.xbase[i_];
+            }
+            deltaxready = true;
+            v = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                v = v+math.sqr(deltax[i]/state.s[i]);
+            }
+            v = Math.Sqrt(v);
+            if( (double)(v)<=(double)(state.epsx) )
+            {
+                if( state.modelage==0 )
+                {
+                    
+                    //
+                    // Step is too short, model is fresh and we can rely on it.
+                    // Terminating.
+                    //
+                    iflag = 2;
+                    goto lbl_3;
+                }
+                else
+                {
+                    
+                    //
+                    // Step is suspiciously short, but model is not fresh
+                    // and we can't rely on it.
+                    //
+                    iflag = -2;
+                    goto lbl_3;
+                }
+            }
+            
+            //
+            // 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
+            //
+            for(i_=0; i_<=n-1;i_++)
+            {
+                state.x[i_] = xnew[i_];
+            }
+            state.needf = false;
+            state.needfi = false;
+            if( !state.hasfi )
+            {
+                goto lbl_4;
+            }
+            state.needfi = true;
+            state.rstate.stage = 0;
+            goto lbl_rcomm;
+        lbl_0:
+            state.needfi = false;
+            v = 0.0;
+            for(i_=0; i_<=m-1;i_++)
+            {
+                v += state.fi[i_]*state.fi[i_];
+            }
+            fnew = v;
+            for(i_=0; i_<=m-1;i_++)
+            {
+                deltaf[i_] = state.fi[i_];
+            }
+            for(i_=0; i_<=m-1;i_++)
+            {
+                deltaf[i_] = deltaf[i_] - state.fibase[i_];
+            }
+            deltafready = true;
+            goto lbl_5;
+        lbl_4:
+            state.needf = true;
+            state.rstate.stage = 1;
+            goto lbl_rcomm;
+        lbl_1:
+            state.needf = false;
+            fnew = state.f;
+        lbl_5:
+            if( !math.isfinite(fnew) )
+            {
+                
+                //
+                // Integrity check failed, break!
+                //
+                iflag = -8;
+                goto lbl_3;
+            }
+            if( (double)(fnew)>=(double)(state.fbase) )
+            {
+                
+                //
+                // Increase lambda and continue
+                //
+                if( !increaselambda(ref lambdav, ref nu, _params) )
+                {
+                    iflag = -1;
+                    goto lbl_3;
+                }
+                goto lbl_2;
+            }
+            
+            //
+            // We've found our step!
+            //
+            iflag = 0;
+            goto lbl_3;
+            goto lbl_2;
+        lbl_3:
+            nu = 1;
+            alglib.ap.assert(((iflag>=-3 && iflag<=0) || iflag==-8) || iflag>0, "MinLM: internal integrity check failed!");
+            result = false;
+            return result;
+            
+            //
+            // Saving state
+            //
+        lbl_rcomm:
+            result = true;
+            state.rstate.ia[0] = i;
+            state.rstate.ia[1] = n;
+            state.rstate.ia[2] = m;
+            state.rstate.ba[0] = bflag;
+            state.rstate.ra[0] = v;
+            return result;
+        }
+
+
+    }
+}
+
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/solvers.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/solvers.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/solvers.cs	(revision 17158)
@@ -0,0 +1,11822 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 164
+#pragma warning disable 219
+using System;
+
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class densesolverreport : alglibobject
+    {
+        //
+        // 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 directdensesolvers.densesolverreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new densesolverreport((directdensesolvers.densesolverreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private directdensesolvers.densesolverreport _innerobj;
+        public directdensesolvers.densesolverreport innerobj { get { return _innerobj; } }
+        public densesolverreport(directdensesolvers.densesolverreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class densesolverlsreport : alglibobject
+    {
+        //
+        // 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 directdensesolvers.densesolverlsreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new densesolverlsreport((directdensesolvers.densesolverlsreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private directdensesolvers.densesolverlsreport _innerobj;
+        public directdensesolvers.densesolverlsreport innerobj { get { return _innerobj; } }
+        public densesolverlsreport(directdensesolvers.densesolverlsreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+    
+    /*************************************************************************
+    Dense solver for A*x=b with N*N real matrix A and N*1 real vectorx  x  and
+    b. This is "slow-but-feature rich" version of the  linear  solver.  Faster
+    version is RMatrixSolveFast() function.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * iterative refinement
+    * O(N^3) complexity
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear  system
+               ! and  performs  iterative   refinement,   which   results   in
+               ! significant performance penalty  when  compared  with  "fast"
+               ! version  which  just  performs  LU  decomposition  and  calls
+               ! triangular solver.
+               !
+               ! This  performance  penalty  is  especially  visible  in   the
+               ! multithreaded mode, because both condition number  estimation
+               ! and   iterative    refinement   are   inherently   sequential
+               ! calculations. It is also very significant on small matrices.
+               !
+               ! Thus, if you need high performance and if you are pretty sure
+               ! that your system is well conditioned, we  strongly  recommend
+               ! you to use faster solver, RMatrixSolveFast() function.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    matrix is very badly conditioned or exactly singular.
+                    * -1    N<=0 was passed
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- 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];
+        directdensesolvers.rmatrixsolve(a, n, b, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void rmatrixsolve(double[,] a, int n, double[] b, out int info, out densesolverreport rep, out double[] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0];
+        directdensesolvers.rmatrixsolve(a, n, b, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver.
+
+    This  subroutine  solves  a  system  A*x=b,  where A is NxN non-denegerate
+    real matrix, x  and  b  are  vectors.  This is a "fast" version of  linear
+    solver which does NOT provide  any  additional  functions  like  condition
+    number estimation or iterative refinement.
+
+    Algorithm features:
+    * efficient algorithm O(N^3) complexity
+    * no performance overhead from additional functionality
+
+    If you need condition number estimation or iterative refinement, use  more
+    feature-rich version - RMatrixSolve().
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    matrix is exactly singular (ill conditioned matrices
+                            are not recognized).
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        B       -   array[N]:
+                    * info>0    =>  overwritten by solution
+                    * info=-3   =>  filled by zeros
+
+      -- ALGLIB --
+         Copyright 16.03.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixsolvefast(double[,] a, int n, ref double[] b, out int info)
+    {
+        info = 0;
+        directdensesolvers.rmatrixsolvefast(a, n, b, ref info, null);
+    }
+    
+    public static void rmatrixsolvefast(double[,] a, int n, ref double[] b, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.rmatrixsolvefast(a, n, b, ref info, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver.
+
+    Similar to RMatrixSolve() but solves task with multiple right parts (where
+    b and x are NxM matrices). This is  "slow-but-robust"  version  of  linear
+    solver with additional functionality  like  condition  number  estimation.
+    There also exists faster version - RMatrixSolveMFast().
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * optional iterative refinement
+    * O(N^3+M*N^2) complexity
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear  system
+               ! and  performs  iterative   refinement,   which   results   in
+               ! significant performance penalty  when  compared  with  "fast"
+               ! version  which  just  performs  LU  decomposition  and  calls
+               ! triangular solver.
+               !
+               ! This  performance  penalty  is  especially  visible  in   the
+               ! multithreaded mode, because both condition number  estimation
+               ! and   iterative    refinement   are   inherently   sequential
+               ! calculations. It also very significant on small matrices.
+               !
+               ! Thus, if you need high performance and if you are pretty sure
+               ! that your system is well conditioned, we  strongly  recommend
+               ! you to use faster solver, RMatrixSolveMFast() function.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    -   return code:
+                    * -3    A is ill conditioned or 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     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+
+      -- 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];
+        directdensesolvers.rmatrixsolvem(a, n, b, m, rfs, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void rmatrixsolvem(double[,] a, int n, double[,] b, int m, bool rfs, out int info, out densesolverreport rep, out double[,] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0,0];
+        directdensesolvers.rmatrixsolvem(a, n, b, m, rfs, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver.
+
+    Similar to RMatrixSolve() but solves task with multiple right parts (where
+    b and x are NxM matrices). This is "fast" version of linear  solver  which
+    does NOT offer additional functions like condition  number  estimation  or
+    iterative refinement.
+
+    Algorithm features:
+    * O(N^3+M*N^2) complexity
+    * no additional functionality, highest performance
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    -   return code:
+                    * -3    matrix is exactly singular (ill conditioned matrices
+                            are not recognized).
+                            X is filled by zeros in such cases.
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        B       -   array[N]:
+                    * info>0    =>  overwritten by solution
+                    * info=-3   =>  filled by zeros
+
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixsolvemfast(double[,] a, int n, ref double[,] b, int m, out int info)
+    {
+        info = 0;
+        directdensesolvers.rmatrixsolvemfast(a, n, b, m, ref info, null);
+    }
+    
+    public static void rmatrixsolvemfast(double[,] a, int n, ref double[,] b, int m, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.rmatrixsolvemfast(a, n, b, m, ref info, _params);
+    }
+    
+    /*************************************************************************
+    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 real vectors.  This
+    is "slow-but-robust" version of the linear LU-based solver. Faster version
+    is RMatrixLUSolveFast() function.
+
+    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.
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear system,
+               ! which results in 10-15x  performance  penalty  when  compared
+               ! with "fast" version which just calls triangular solver.
+               !
+               ! This performance penalty is insignificant  when compared with
+               ! cost of large LU decomposition.  However,  if you  call  this
+               ! function many times for the same  left  side,  this  overhead
+               ! BECOMES significant. It  also  becomes significant for small-
+               ! scale problems.
+               !
+               ! In such cases we strongly recommend you to use faster solver,
+               ! RMatrixLUSolveFast() function.
+
+    INPUT PARAMETERS
+        LUA     -   array[N,N], LU decomposition, RMatrixLU result
+        P       -   array[N], pivots array, RMatrixLU result
+        N       -   size of A
+        B       -   array[N], right part
+
+    OUTPUT PARAMETERS
+        Info    -   return code:
+                    * -3    matrix is very badly conditioned or exactly singular.
+                    * -1    N<=0 was passed
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+
+      -- 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];
+        directdensesolvers.rmatrixlusolve(lua, p, n, b, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void rmatrixlusolve(double[,] lua, int[] p, int n, double[] b, out int info, out densesolverreport rep, out double[] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0];
+        directdensesolvers.rmatrixlusolve(lua, p, n, b, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    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 real vectors.  This
+    is "fast-without-any-checks" version of the linear LU-based solver. Slower
+    but more robust version is RMatrixLUSolve() function.
+
+    Algorithm features:
+    * O(N^2) complexity
+    * fast algorithm without ANY additional checks, just triangular solver
+
+    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    -   return code:
+                    * -3    matrix is exactly singular (ill conditioned matrices
+                            are not recognized).
+                            X is filled by zeros in such cases.
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        B       -   array[N]:
+                    * info>0    =>  overwritten by solution
+                    * info=-3   =>  filled by zeros
+
+      -- ALGLIB --
+         Copyright 18.03.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixlusolvefast(double[,] lua, int[] p, int n, ref double[] b, out int info)
+    {
+        info = 0;
+        directdensesolvers.rmatrixlusolvefast(lua, p, n, b, ref info, null);
+    }
+    
+    public static void rmatrixlusolvefast(double[,] lua, int[] p, int n, ref double[] b, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.rmatrixlusolvefast(lua, p, n, b, ref info, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver.
+
+    Similar to RMatrixLUSolve() but solves  task  with  multiple  right  parts
+    (where b and x are NxM matrices). This  is  "robust-but-slow"  version  of
+    LU-based solver which performs additional  checks  for  non-degeneracy  of
+    inputs (condition number estimation). If you need  best  performance,  use
+    "fast-without-any-checks" version, RMatrixLUSolveMFast().
+
+    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.
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear system,
+               ! which  results  in  significant  performance   penalty   when
+               ! compared with "fast"  version  which  just  calls  triangular
+               ! solver.
+               !
+               ! This performance penalty is especially apparent when you  use
+               ! ALGLIB parallel capabilities (condition number estimation  is
+               ! inherently  sequential).  It   also   becomes significant for
+               ! small-scale problems.
+               !
+               ! In such cases we strongly recommend you to use faster solver,
+               ! RMatrixLUSolveMFast() function.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS
+        LUA     -   array[N,N], LU decomposition, RMatrixLU result
+        P       -   array[N], 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    -   return code:
+                    * -3    matrix is very badly conditioned or exactly 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     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N,M], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+
+      -- 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];
+        directdensesolvers.rmatrixlusolvem(lua, p, n, b, m, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void rmatrixlusolvem(double[,] lua, int[] p, int n, double[,] b, int m, out int info, out densesolverreport rep, out double[,] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0,0];
+        directdensesolvers.rmatrixlusolvem(lua, p, n, b, m, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver.
+
+    Similar to RMatrixLUSolve() but solves  task  with  multiple  right parts,
+    where b and x are NxM matrices.  This is "fast-without-any-checks" version
+    of LU-based solver. It does not estimate  condition number  of  a  system,
+    so it is extremely fast. If you need better detection  of  near-degenerate
+    cases, use RMatrixLUSolveM() function.
+
+    Algorithm features:
+    * O(M*N^2) complexity
+    * fast algorithm without ANY additional checks, just triangular solver
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    -   return code:
+                    * -3    matrix is exactly singular (ill conditioned matrices
+                            are not recognized).
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        B       -   array[N,M]:
+                    * info>0    =>  overwritten by solution
+                    * info=-3   =>  filled by zeros
+
+      -- ALGLIB --
+         Copyright 18.03.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rmatrixlusolvemfast(double[,] lua, int[] p, int n, ref double[,] b, int m, out int info)
+    {
+        info = 0;
+        directdensesolvers.rmatrixlusolvemfast(lua, p, n, b, m, ref info, null);
+    }
+    
+    public static void rmatrixlusolvemfast(double[,] lua, int[] p, int n, ref double[,] b, int m, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.rmatrixlusolvemfast(lua, p, n, b, m, ref info, _params);
+    }
+    
+    /*************************************************************************
+    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    -   return code:
+                    * -3    matrix is very badly conditioned or exactly singular.
+                    * -1    N<=0 was passed
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- 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];
+        directdensesolvers.rmatrixmixedsolve(a, lua, p, n, b, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void rmatrixmixedsolve(double[,] a, double[,] lua, int[] p, int n, double[] b, out int info, out densesolverreport rep, out double[] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0];
+        directdensesolvers.rmatrixmixedsolve(a, lua, p, n, b, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    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    -   return code:
+                    * -3    matrix is very badly conditioned or exactly singular.
+                    * -1    N<=0 was passed
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N,M], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- 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];
+        directdensesolvers.rmatrixmixedsolvem(a, lua, p, n, b, m, ref info, rep.innerobj, ref x, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0,0];
+        directdensesolvers.rmatrixmixedsolvem(a, lua, p, n, b, m, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Complex dense solver for A*X=B with N*N  complex  matrix  A,  N*M  complex
+    matrices  X  and  B.  "Slow-but-feature-rich"   version   which   provides
+    additional functions, at the cost of slower  performance.  Faster  version
+    may be invoked with CMatrixSolveMFast() function.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * iterative refinement
+    * O(N^3+M*N^2) complexity
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear  system
+               ! and  performs  iterative   refinement,   which   results   in
+               ! significant performance penalty  when  compared  with  "fast"
+               ! version  which  just  performs  LU  decomposition  and  calls
+               ! triangular solver.
+               !
+               ! This  performance  penalty  is  especially  visible  in   the
+               ! multithreaded mode, because both condition number  estimation
+               ! and   iterative    refinement   are   inherently   sequential
+               ! calculations.
+               !
+               ! Thus, if you need high performance and if you are pretty sure
+               ! that your system is well conditioned, we  strongly  recommend
+               ! you to use faster solver, CMatrixSolveMFast() function.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    -   return code:
+                    * -3    matrix is very badly conditioned or exactly 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     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N,M], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- 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];
+        directdensesolvers.cmatrixsolvem(a, n, b, m, rfs, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void cmatrixsolvem(complex[,] a, int n, complex[,] b, int m, bool rfs, out int info, out densesolverreport rep, out complex[,] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0,0];
+        directdensesolvers.cmatrixsolvem(a, n, b, m, rfs, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Complex dense solver for A*X=B with N*N  complex  matrix  A,  N*M  complex
+    matrices  X  and  B.  "Fast-but-lightweight" version which  provides  just
+    triangular solver - and no additional functions like iterative  refinement
+    or condition number estimation.
+
+    Algorithm features:
+    * O(N^3+M*N^2) complexity
+    * no additional time consuming functions
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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
+
+    OUTPUT PARAMETERS:
+        Info    -   return code:
+                    * -3    matrix is exactly singular (ill conditioned matrices
+                            are not recognized).
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        B       -   array[N,M]:
+                    * info>0    =>  overwritten by solution
+                    * info=-3   =>  filled by zeros
+
+      -- ALGLIB --
+         Copyright 16.03.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixsolvemfast(complex[,] a, int n, ref complex[,] b, int m, out int info)
+    {
+        info = 0;
+        directdensesolvers.cmatrixsolvemfast(a, n, b, m, ref info, null);
+    }
+    
+    public static void cmatrixsolvemfast(complex[,] a, int n, ref complex[,] b, int m, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.cmatrixsolvemfast(a, n, b, m, ref info, _params);
+    }
+    
+    /*************************************************************************
+    Complex dense solver for A*x=B with N*N complex matrix A and  N*1  complex
+    vectors x and b. "Slow-but-feature-rich" version of the solver.
+
+    Algorithm features:
+    * automatic detection of degenerate cases
+    * condition number estimation
+    * iterative refinement
+    * O(N^3) complexity
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear  system
+               ! and  performs  iterative   refinement,   which   results   in
+               ! significant performance penalty  when  compared  with  "fast"
+               ! version  which  just  performs  LU  decomposition  and  calls
+               ! triangular solver.
+               !
+               ! This  performance  penalty  is  especially  visible  in   the
+               ! multithreaded mode, because both condition number  estimation
+               ! and   iterative    refinement   are   inherently   sequential
+               ! calculations.
+               !
+               ! Thus, if you need high performance and if you are pretty sure
+               ! that your system is well conditioned, we  strongly  recommend
+               ! you to use faster solver, CMatrixSolveFast() function.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    matrix is very badly conditioned or exactly singular.
+                    * -1    N<=0 was passed
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- 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];
+        directdensesolvers.cmatrixsolve(a, n, b, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void cmatrixsolve(complex[,] a, int n, complex[] b, out int info, out densesolverreport rep, out complex[] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0];
+        directdensesolvers.cmatrixsolve(a, n, b, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Complex dense solver for A*x=B with N*N complex matrix A and  N*1  complex
+    vectors x and b. "Fast-but-lightweight" version of the solver.
+
+    Algorithm features:
+    * O(N^3) complexity
+    * no additional time consuming features, just triangular solver
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    matrix is exactly singular (ill conditioned matrices
+                            are not recognized).
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        B       -   array[N]:
+                    * info>0    =>  overwritten by solution
+                    * info=-3   =>  filled by zeros
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixsolvefast(complex[,] a, int n, ref complex[] b, out int info)
+    {
+        info = 0;
+        directdensesolvers.cmatrixsolvefast(a, n, b, ref info, null);
+    }
+    
+    public static void cmatrixsolvefast(complex[,] a, int n, ref complex[] b, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.cmatrixsolvefast(a, n, b, ref info, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*X=B with N*N complex A given by its  LU  decomposition,
+    and N*M matrices X and B (multiple right sides).   "Slow-but-feature-rich"
+    version of the solver.
+
+    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.
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear system,
+               ! which  results  in  significant  performance   penalty   when
+               ! compared with "fast"  version  which  just  calls  triangular
+               ! solver.
+               !
+               ! This performance penalty is especially apparent when you  use
+               ! ALGLIB parallel capabilities (condition number estimation  is
+               ! inherently  sequential).  It   also   becomes significant for
+               ! small-scale problems.
+               !
+               ! In such cases we strongly recommend you to use faster solver,
+               ! CMatrixLUSolveMFast() function.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    -   return code:
+                    * -3    matrix is very badly conditioned or exactly singular.
+                    * -1    N<=0 was passed
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N,M], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- 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];
+        directdensesolvers.cmatrixlusolvem(lua, p, n, b, m, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void cmatrixlusolvem(complex[,] lua, int[] p, int n, complex[,] b, int m, out int info, out densesolverreport rep, out complex[,] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0,0];
+        directdensesolvers.cmatrixlusolvem(lua, p, n, b, m, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*X=B with N*N complex A given by its  LU  decomposition,
+    and N*M matrices X and B (multiple  right  sides).  "Fast-but-lightweight"
+    version of the solver.
+
+    Algorithm features:
+    * O(M*N^2) complexity
+    * no additional time-consuming features
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    -   return code:
+                    * -3    matrix is exactly singular (ill conditioned matrices
+                            are not recognized).
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        B       -   array[N,M]:
+                    * info>0    =>  overwritten by solution
+                    * info=-3   =>  filled by zeros
+
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixlusolvemfast(complex[,] lua, int[] p, int n, ref complex[,] b, int m, out int info)
+    {
+        info = 0;
+        directdensesolvers.cmatrixlusolvemfast(lua, p, n, b, m, ref info, null);
+    }
+    
+    public static void cmatrixlusolvemfast(complex[,] lua, int[] p, int n, ref complex[,] b, int m, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.cmatrixlusolvemfast(lua, p, n, b, m, ref info, _params);
+    }
+    
+    /*************************************************************************
+    Complex dense linear solver for A*x=b with complex N*N A  given  by its LU
+    decomposition and N*1 vectors x and b. This is  "slow-but-robust"  version
+    of  the  complex  linear  solver  with  additional  features   which   add
+    significant performance overhead. Faster version  is  CMatrixLUSolveFast()
+    function.
+
+    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.
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear system,
+               ! which results in 10-15x  performance  penalty  when  compared
+               ! with "fast" version which just calls triangular solver.
+               !
+               ! This performance penalty is insignificant  when compared with
+               ! cost of large LU decomposition.  However,  if you  call  this
+               ! function many times for the same  left  side,  this  overhead
+               ! BECOMES significant. It  also  becomes significant for small-
+               ! scale problems.
+               !
+               ! In such cases we strongly recommend you to use faster solver,
+               ! CMatrixLUSolveFast() function.
+
+    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    -   return code:
+                    * -3    matrix is very badly conditioned or exactly singular.
+                    * -1    N<=0 was passed
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- 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];
+        directdensesolvers.cmatrixlusolve(lua, p, n, b, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void cmatrixlusolve(complex[,] lua, int[] p, int n, complex[] b, out int info, out densesolverreport rep, out complex[] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0];
+        directdensesolvers.cmatrixlusolve(lua, p, n, b, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Complex dense linear solver for A*x=b with N*N complex A given by  its  LU
+    decomposition and N*1 vectors x and b. This is  fast  lightweight  version
+    of solver, which is significantly faster than CMatrixLUSolve(),  but  does
+    not provide additional information (like condition numbers).
+
+    Algorithm features:
+    * O(N^2) complexity
+    * no additional time-consuming features, just triangular solver
+
+    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    -   return code:
+                    * -3    matrix is exactly singular (ill conditioned matrices
+                            are not recognized).
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        B       -   array[N]:
+                    * info>0    =>  overwritten by solution
+                    * info=-3   =>  filled by zeros
+
+    NOTE: unlike  CMatrixLUSolve(),  this   function   does   NOT   check  for
+          near-degeneracy of input matrix. It  checks  for  EXACT  degeneracy,
+          because this check is easy to do. However,  very  badly  conditioned
+          matrices may went unnoticed.
+
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void cmatrixlusolvefast(complex[,] lua, int[] p, int n, ref complex[] b, out int info)
+    {
+        info = 0;
+        directdensesolvers.cmatrixlusolvefast(lua, p, n, b, ref info, null);
+    }
+    
+    public static void cmatrixlusolvefast(complex[,] lua, int[] p, int n, ref complex[] b, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.cmatrixlusolvefast(lua, p, n, b, ref info, _params);
+    }
+    
+    /*************************************************************************
+    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    -   return code:
+                    * -3    matrix is very badly conditioned or exactly singular.
+                    * -1    N<=0 was passed
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N,M], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- 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];
+        directdensesolvers.cmatrixmixedsolvem(a, lua, p, n, b, m, ref info, rep.innerobj, ref x, null);
+    }
+    
+    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, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0,0];
+        directdensesolvers.cmatrixmixedsolvem(a, lua, p, n, b, m, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    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    -   return code:
+                    * -3    matrix is very badly conditioned or exactly singular.
+                    * -1    N<=0 was passed
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- 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];
+        directdensesolvers.cmatrixmixedsolve(a, lua, p, n, b, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void cmatrixmixedsolve(complex[,] a, complex[,] lua, int[] p, int n, complex[] b, out int info, out densesolverreport rep, out complex[] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0];
+        directdensesolvers.cmatrixmixedsolve(a, lua, p, n, b, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*X=B with N*N symmetric positive definite matrix A,  and
+    N*M vectors X and B. It is "slow-but-feature-rich" version of the solver.
+
+    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.
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear system,
+               ! which  results  in  significant   performance   penalty  when
+               ! compared with "fast" version  which  just  performs  Cholesky
+               ! decomposition and calls triangular solver.
+               !
+               ! This  performance  penalty  is  especially  visible  in   the
+               ! multithreaded mode, because both condition number  estimation
+               ! and   iterative    refinement   are   inherently   sequential
+               ! calculations.
+               !
+               ! Thus, if you need high performance and if you are pretty sure
+               ! that your system is well conditioned, we  strongly  recommend
+               ! you to use faster solver, SPDMatrixSolveMFast() function.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    -   return code:
+                    * -3    matrix is very badly conditioned or non-SPD.
+                    * -1    N<=0 was passed
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N,M], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- 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];
+        directdensesolvers.spdmatrixsolvem(a, n, isupper, b, m, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void spdmatrixsolvem(double[,] a, int n, bool isupper, double[,] b, int m, out int info, out densesolverreport rep, out double[,] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0,0];
+        directdensesolvers.spdmatrixsolvem(a, n, isupper, b, m, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*X=B with N*N symmetric positive definite matrix A,  and
+    N*M vectors X and B. It is "fast-but-lightweight" version of the solver.
+
+    Algorithm features:
+    * O(N^3+M*N^2) complexity
+    * matrix is represented by its upper or lower triangle
+    * no additional time consuming features
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    -   return code:
+                    * -3    A is is exactly singular
+                    * -1    N<=0 was passed
+                    *  1    task was solved
+        B       -   array[N,M], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- ALGLIB --
+         Copyright 17.03.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spdmatrixsolvemfast(double[,] a, int n, bool isupper, ref double[,] b, int m, out int info)
+    {
+        info = 0;
+        directdensesolvers.spdmatrixsolvemfast(a, n, isupper, b, m, ref info, null);
+    }
+    
+    public static void spdmatrixsolvemfast(double[,] a, int n, bool isupper, ref double[,] b, int m, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.spdmatrixsolvemfast(a, n, isupper, b, m, ref info, _params);
+    }
+    
+    /*************************************************************************
+    Dense linear solver for A*x=b with N*N real  symmetric  positive  definite
+    matrix A,  N*1 vectors x and b.  "Slow-but-feature-rich"  version  of  the
+    solver.
+
+    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.
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear system,
+               ! which  results  in  significant   performance   penalty  when
+               ! compared with "fast" version  which  just  performs  Cholesky
+               ! decomposition and calls triangular solver.
+               !
+               ! This  performance  penalty  is  especially  visible  in   the
+               ! multithreaded mode, because both condition number  estimation
+               ! and   iterative    refinement   are   inherently   sequential
+               ! calculations.
+               !
+               ! Thus, if you need high performance and if you are pretty sure
+               ! that your system is well conditioned, we  strongly  recommend
+               ! you to use faster solver, SPDMatrixSolveFast() function.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    -   return code:
+                    * -3    matrix is very badly conditioned or non-SPD.
+                    * -1    N<=0 was passed
+                    *  1    task is solved (but matrix A may be ill-conditioned,
+                            check R1/RInf parameters for condition numbers).
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- 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];
+        directdensesolvers.spdmatrixsolve(a, n, isupper, b, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void spdmatrixsolve(double[,] a, int n, bool isupper, double[] b, out int info, out densesolverreport rep, out double[] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0];
+        directdensesolvers.spdmatrixsolve(a, n, isupper, b, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense linear solver for A*x=b with N*N real  symmetric  positive  definite
+    matrix A,  N*1 vectors x and  b.  "Fast-but-lightweight"  version  of  the
+    solver.
+
+    Algorithm features:
+    * O(N^3) complexity
+    * matrix is represented by its upper or lower triangle
+    * no additional time consuming features like condition number estimation
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    -   return code:
+                    * -3    A is is exactly singular or non-SPD
+                    * -1    N<=0 was passed
+                    *  1    task was solved
+        B       -   array[N], it contains:
+                    * info>0    =>  solution
+                    * info=-3   =>  filled by zeros
+
+      -- ALGLIB --
+         Copyright 17.03.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spdmatrixsolvefast(double[,] a, int n, bool isupper, ref double[] b, out int info)
+    {
+        info = 0;
+        directdensesolvers.spdmatrixsolvefast(a, n, isupper, b, ref info, null);
+    }
+    
+    public static void spdmatrixsolvefast(double[,] a, int n, bool isupper, ref double[] b, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.spdmatrixsolvefast(a, n, isupper, b, ref info, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*X=B with N*N symmetric positive definite matrix A given
+    by its Cholesky decomposition, and N*M vectors X and B. It  is  "slow-but-
+    feature-rich" version of the solver which estimates  condition  number  of
+    the system.
+
+    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.
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear system,
+               ! which  results  in  significant  performance   penalty   when
+               ! compared with "fast"  version  which  just  calls  triangular
+               ! solver. Amount of  overhead  introduced  depends  on  M  (the
+               ! larger - the more efficient).
+               !
+               ! This performance penalty is insignificant  when compared with
+               ! cost of large LU decomposition.  However,  if you  call  this
+               ! function many times for the same  left  side,  this  overhead
+               ! BECOMES significant. It  also  becomes significant for small-
+               ! scale problems (N<50).
+               !
+               ! In such cases we strongly recommend you to use faster solver,
+               ! SPDMatrixCholeskySolveMFast() function.
+
+    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    -   return code:
+                    * -3    A is is exactly singular or badly conditioned
+                            X is filled by zeros in such cases.
+                    * -1    N<=0 was passed
+                    *  1    task was solved
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N]:
+                    * for info>0 contains solution
+                    * for info=-3 filled by zeros
+
+      -- 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];
+        directdensesolvers.spdmatrixcholeskysolvem(cha, n, isupper, b, m, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void spdmatrixcholeskysolvem(double[,] cha, int n, bool isupper, double[,] b, int m, out int info, out densesolverreport rep, out double[,] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0,0];
+        directdensesolvers.spdmatrixcholeskysolvem(cha, n, isupper, b, m, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*X=B with N*N symmetric positive definite matrix A given
+    by its Cholesky decomposition, and N*M vectors X and B. It  is  "fast-but-
+    lightweight" version of  the  solver  which  just  solves  linear  system,
+    without any additional functions.
+
+    Algorithm features:
+    * O(M*N^2) complexity
+    * matrix is represented by its upper or lower triangle
+    * no additional functionality
+
+    INPUT PARAMETERS
+        CHA     -   array[N,N], Cholesky decomposition,
+                    SPDMatrixCholesky result
+        N       -   size of CHA
+        IsUpper -   what half of CHA is provided
+        B       -   array[N,M], right part
+        M       -   right part size
+
+    OUTPUT PARAMETERS
+        Info    -   return code:
+                    * -3    A is is exactly singular or badly conditioned
+                            X is filled by zeros in such cases.
+                    * -1    N<=0 was passed
+                    *  1    task was solved
+        B       -   array[N]:
+                    * for info>0 overwritten by solution
+                    * for info=-3 filled by zeros
+
+      -- ALGLIB --
+         Copyright 18.03.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spdmatrixcholeskysolvemfast(double[,] cha, int n, bool isupper, ref double[,] b, int m, out int info)
+    {
+        info = 0;
+        directdensesolvers.spdmatrixcholeskysolvemfast(cha, n, isupper, b, m, ref info, null);
+    }
+    
+    public static void spdmatrixcholeskysolvemfast(double[,] cha, int n, bool isupper, ref double[,] b, int m, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.spdmatrixcholeskysolvemfast(cha, n, isupper, b, m, ref info, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*x=b with N*N symmetric positive definite matrix A given
+    by its Cholesky decomposition, and N*1 real vectors x and b. This is "slow-
+    but-feature-rich"  version  of  the  solver  which,  in  addition  to  the
+    solution, performs condition number estimation.
+
+    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.
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear system,
+               ! which results in 10-15x  performance  penalty  when  compared
+               ! with "fast" version which just calls triangular solver.
+               !
+               ! This performance penalty is insignificant  when compared with
+               ! cost of large LU decomposition.  However,  if you  call  this
+               ! function many times for the same  left  side,  this  overhead
+               ! BECOMES significant. It  also  becomes significant for small-
+               ! scale problems (N<50).
+               !
+               ! In such cases we strongly recommend you to use faster solver,
+               ! SPDMatrixCholeskySolveFast() function.
+
+    INPUT PARAMETERS
+        CHA     -   array[N,N], Cholesky decomposition,
+                    SPDMatrixCholesky result
+        N       -   size of A
+        IsUpper -   what half of CHA is provided
+        B       -   array[N], right part
+
+    OUTPUT PARAMETERS
+        Info    -   return code:
+                    * -3    A is is exactly singular or ill conditioned
+                            X is filled by zeros in such cases.
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N]:
+                    * for info>0  - solution
+                    * for info=-3 - filled by zeros
+
+      -- 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];
+        directdensesolvers.spdmatrixcholeskysolve(cha, n, isupper, b, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void spdmatrixcholeskysolve(double[,] cha, int n, bool isupper, double[] b, out int info, out densesolverreport rep, out double[] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new double[0];
+        directdensesolvers.spdmatrixcholeskysolve(cha, n, isupper, b, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*x=b with N*N symmetric positive definite matrix A given
+    by its Cholesky decomposition, and N*1 real vectors x and b. This is "fast-
+    but-lightweight" version of the solver.
+
+    Algorithm features:
+    * O(N^2) complexity
+    * matrix is represented by its upper or lower triangle
+    * no additional features
+
+    INPUT PARAMETERS
+        CHA     -   array[N,N], Cholesky decomposition,
+                    SPDMatrixCholesky result
+        N       -   size of A
+        IsUpper -   what half of CHA is provided
+        B       -   array[N], right part
+
+    OUTPUT PARAMETERS
+        Info    -   return code:
+                    * -3    A is is exactly singular or ill conditioned
+                            X is filled by zeros in such cases.
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        B       -   array[N]:
+                    * for info>0  - overwritten by solution
+                    * for info=-3 - filled by zeros
+
+      -- ALGLIB --
+         Copyright 27.01.2010 by Bochkanov Sergey
+    *************************************************************************/
+    public static void spdmatrixcholeskysolvefast(double[,] cha, int n, bool isupper, ref double[] b, out int info)
+    {
+        info = 0;
+        directdensesolvers.spdmatrixcholeskysolvefast(cha, n, isupper, b, ref info, null);
+    }
+    
+    public static void spdmatrixcholeskysolvefast(double[,] cha, int n, bool isupper, ref double[] b, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.spdmatrixcholeskysolvefast(cha, n, isupper, b, ref info, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*X=B, with N*N Hermitian positive definite matrix A  and
+    N*M  complex  matrices  X  and  B.  "Slow-but-feature-rich" version of the
+    solver.
+
+    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.
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear system,
+               ! which  results  in  significant  performance   penalty   when
+               ! compared with "fast"  version  which  just  calls  triangular
+               ! solver.
+               !
+               ! This performance penalty is especially apparent when you  use
+               ! ALGLIB parallel capabilities (condition number estimation  is
+               ! inherently  sequential).  It   also   becomes significant for
+               ! small-scale problems (N<100).
+               !
+               ! In such cases we strongly recommend you to use faster solver,
+               ! HPDMatrixSolveMFast() function.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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];
+        directdensesolvers.hpdmatrixsolvem(a, n, isupper, b, m, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void hpdmatrixsolvem(complex[,] a, int n, bool isupper, complex[,] b, int m, out int info, out densesolverreport rep, out complex[,] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0,0];
+        directdensesolvers.hpdmatrixsolvem(a, n, isupper, b, m, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*X=B, with N*N Hermitian positive definite matrix A  and
+    N*M complex matrices X and B. "Fast-but-lightweight" version of the solver.
+
+    Algorithm features:
+    * O(N^3+M*N^2) complexity
+    * matrix is represented by its upper or lower triangle
+    * no additional time consuming features like condition number estimation
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    -   return code:
+                    * -3    A is is exactly  singular or is not positive definite.
+                            B is filled by zeros in such cases.
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        B       -   array[0..N-1]:
+                    * overwritten by solution
+                    * zeros, if problem was not solved
+
+      -- ALGLIB --
+         Copyright 17.03.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hpdmatrixsolvemfast(complex[,] a, int n, bool isupper, ref complex[,] b, int m, out int info)
+    {
+        info = 0;
+        directdensesolvers.hpdmatrixsolvemfast(a, n, isupper, b, m, ref info, null);
+    }
+    
+    public static void hpdmatrixsolvemfast(complex[,] a, int n, bool isupper, ref complex[,] b, int m, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.hpdmatrixsolvemfast(a, n, isupper, b, m, ref info, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*x=b, with N*N Hermitian positive definite matrix A, and
+    N*1 complex vectors  x  and  b.  "Slow-but-feature-rich"  version  of  the
+    solver.
+
+    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.
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear system,
+               ! which  results  in  significant   performance   penalty  when
+               ! compared with "fast" version  which  just  performs  Cholesky
+               ! decomposition and calls triangular solver.
+               !
+               ! This  performance  penalty  is  especially  visible  in   the
+               ! multithreaded mode, because both condition number  estimation
+               ! and   iterative    refinement   are   inherently   sequential
+               ! calculations.
+               !
+               ! Thus, if you need high performance and if you are pretty sure
+               ! that your system is well conditioned, we  strongly  recommend
+               ! you to use faster solver, HPDMatrixSolveFast() function.
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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];
+        directdensesolvers.hpdmatrixsolve(a, n, isupper, b, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void hpdmatrixsolve(complex[,] a, int n, bool isupper, complex[] b, out int info, out densesolverreport rep, out complex[] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0];
+        directdensesolvers.hpdmatrixsolve(a, n, isupper, b, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*x=b, with N*N Hermitian positive definite matrix A, and
+    N*1 complex vectors  x  and  b.  "Fast-but-lightweight"  version  of   the
+    solver without additional functions.
+
+    Algorithm features:
+    * O(N^3) complexity
+    * matrix is represented by its upper or lower triangle
+    * no additional time consuming functions
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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    -   return code:
+                    * -3    A is is exactly singular or not positive definite
+                            X is filled by zeros in such cases.
+                    * -1    N<=0 was passed
+                    *  1    task was solved
+        B       -   array[0..N-1]:
+                    * overwritten by solution
+                    * zeros, if A is exactly singular (diagonal of its LU
+                      decomposition has exact zeros).
+
+      -- ALGLIB --
+         Copyright 17.03.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hpdmatrixsolvefast(complex[,] a, int n, bool isupper, ref complex[] b, out int info)
+    {
+        info = 0;
+        directdensesolvers.hpdmatrixsolvefast(a, n, isupper, b, ref info, null);
+    }
+    
+    public static void hpdmatrixsolvefast(complex[,] a, int n, bool isupper, ref complex[] b, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.hpdmatrixsolvefast(a, n, isupper, b, ref info, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*X=B with N*N Hermitian positive definite matrix A given
+    by its Cholesky decomposition and N*M complex matrices X  and  B.  This is
+    "slow-but-feature-rich" version of the solver which, in  addition  to  the
+    solution, estimates condition number of the system.
+
+    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.
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear system,
+               ! which  results  in  significant  performance   penalty   when
+               ! compared with "fast"  version  which  just  calls  triangular
+               ! solver. Amount of  overhead  introduced  depends  on  M  (the
+               ! larger - the more efficient).
+               !
+               ! This performance penalty is insignificant  when compared with
+               ! cost of large Cholesky decomposition.  However,  if  you call
+               ! this  function  many  times  for  the same  left  side,  this
+               ! overhead BECOMES significant. It  also   becomes  significant
+               ! for small-scale problems (N<50).
+               !
+               ! In such cases we strongly recommend you to use faster solver,
+               ! HPDMatrixCholeskySolveMFast() function.
+
+
+    INPUT PARAMETERS
+        CHA     -   array[N,N], Cholesky decomposition,
+                    HPDMatrixCholesky result
+        N       -   size of CHA
+        IsUpper -   what half of CHA is provided
+        B       -   array[N,M], right part
+        M       -   right part size
+
+    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 was solved
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N]:
+                    * for info>0 contains solution
+                    * for info=-3 filled by zeros
+
+      -- 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];
+        directdensesolvers.hpdmatrixcholeskysolvem(cha, n, isupper, b, m, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void hpdmatrixcholeskysolvem(complex[,] cha, int n, bool isupper, complex[,] b, int m, out int info, out densesolverreport rep, out complex[,] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0,0];
+        directdensesolvers.hpdmatrixcholeskysolvem(cha, n, isupper, b, m, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*X=B with N*N Hermitian positive definite matrix A given
+    by its Cholesky decomposition and N*M complex matrices X  and  B.  This is
+    "fast-but-lightweight" version of the solver.
+
+    Algorithm features:
+    * O(M*N^2) complexity
+    * matrix is represented by its upper or lower triangle
+    * no additional time-consuming features
+
+    INPUT PARAMETERS
+        CHA     -   array[N,N], Cholesky decomposition,
+                    HPDMatrixCholesky result
+        N       -   size of CHA
+        IsUpper -   what half of CHA is provided
+        B       -   array[N,M], right part
+        M       -   right part size
+
+    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 was solved
+        B       -   array[N]:
+                    * for info>0 overwritten by solution
+                    * for info=-3 filled by zeros
+
+      -- ALGLIB --
+         Copyright 18.03.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hpdmatrixcholeskysolvemfast(complex[,] cha, int n, bool isupper, ref complex[,] b, int m, out int info)
+    {
+        info = 0;
+        directdensesolvers.hpdmatrixcholeskysolvemfast(cha, n, isupper, b, m, ref info, null);
+    }
+    
+    public static void hpdmatrixcholeskysolvemfast(complex[,] cha, int n, bool isupper, ref complex[,] b, int m, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.hpdmatrixcholeskysolvemfast(cha, n, isupper, b, m, ref info, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*x=b with N*N Hermitian positive definite matrix A given
+    by its Cholesky decomposition, and N*1 complex vectors x and  b.  This  is
+    "slow-but-feature-rich" version of the solver  which  estimates  condition
+    number of the system.
+
+    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.
+
+    IMPORTANT: ! this function is NOT the most efficient linear solver provided
+               ! by ALGLIB. It estimates condition  number  of  linear system,
+               ! which results in 10-15x  performance  penalty  when  compared
+               ! with "fast" version which just calls triangular solver.
+               !
+               ! This performance penalty is insignificant  when compared with
+               ! cost of large LU decomposition.  However,  if you  call  this
+               ! function many times for the same  left  side,  this  overhead
+               ! BECOMES significant. It  also  becomes significant for small-
+               ! scale problems (N<50).
+               !
+               ! In such cases we strongly recommend you to use faster solver,
+               ! HPDMatrixCholeskySolveFast() function.
+
+    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    -   return code:
+                    * -3    A is is exactly singular or ill conditioned
+                            X is filled by zeros in such cases.
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        Rep     -   additional report, following fields are set:
+                    * rep.r1    condition number in 1-norm
+                    * rep.rinf  condition number in inf-norm
+        X       -   array[N]:
+                    * for info>0  - solution
+                    * for info=-3 - filled by zeros
+
+      -- 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];
+        directdensesolvers.hpdmatrixcholeskysolve(cha, n, isupper, b, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void hpdmatrixcholeskysolve(complex[,] cha, int n, bool isupper, complex[] b, out int info, out densesolverreport rep, out complex[] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverreport();
+        x = new complex[0];
+        directdensesolvers.hpdmatrixcholeskysolve(cha, n, isupper, b, ref info, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Dense solver for A*x=b with N*N Hermitian positive definite matrix A given
+    by its Cholesky decomposition, and N*1 complex vectors x and  b.  This  is
+    "fast-but-lightweight" version of the solver.
+
+    Algorithm features:
+    * O(N^2) complexity
+    * matrix is represented by its upper or lower triangle
+    * no additional time-consuming features
+
+    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    -   return code:
+                    * -3    A is is exactly singular or ill conditioned
+                            B is filled by zeros in such cases.
+                    * -1    N<=0 was passed
+                    *  1    task is solved
+        B       -   array[N]:
+                    * for info>0  - overwritten by solution
+                    * for info=-3 - filled by zeros
+
+      -- ALGLIB --
+         Copyright 18.03.2015 by Bochkanov Sergey
+    *************************************************************************/
+    public static void hpdmatrixcholeskysolvefast(complex[,] cha, int n, bool isupper, ref complex[] b, out int info)
+    {
+        info = 0;
+        directdensesolvers.hpdmatrixcholeskysolvefast(cha, n, isupper, b, ref info, null);
+    }
+    
+    public static void hpdmatrixcholeskysolvefast(complex[,] cha, int n, bool isupper, ref complex[] b, out int info, alglib.xparams _params)
+    {
+        info = 0;
+        directdensesolvers.hpdmatrixcholeskysolvefast(cha, n, isupper, b, ref info, _params);
+    }
+    
+    /*************************************************************************
+    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 (and correct handling!) of degenerate cases
+    * iterative refinement
+    * O(N^3) complexity
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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 (even for singular A)
+                    * zeros, if SVD subroutine failed
+
+    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];
+        directdensesolvers.rmatrixsolvels(a, nrows, ncols, b, threshold, ref info, rep.innerobj, ref x, null);
+    }
+    
+    public static void rmatrixsolvels(double[,] a, int nrows, int ncols, double[] b, double threshold, out int info, out densesolverlsreport rep, out double[] x, alglib.xparams _params)
+    {
+        info = 0;
+        rep = new densesolverlsreport();
+        x = new double[0];
+        directdensesolvers.rmatrixsolvels(a, nrows, ncols, b, threshold, ref info, rep.innerobj, ref x, _params);
+    }
+
+}
+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 : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public linlsqrstate()
+        {
+            _innerobj = new linlsqr.linlsqrstate();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new linlsqrstate((linlsqr.linlsqrstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new linlsqrreport((linlsqr.linlsqrreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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
+
+    NOTE: see also linlsqrcreatebuf()  for  version  which  reuses  previously
+          allocated place as much as possible.
+
+      -- 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, null);
+    }
+    
+    public static void linlsqrcreate(int m, int n, out linlsqrstate state, alglib.xparams _params)
+    {
+        state = new linlsqrstate();
+        linlsqr.linlsqrcreate(m, n, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This function initializes linear LSQR Solver.  It  provides  exactly  same
+    functionality as linlsqrcreate(), but reuses  previously  allocated  space
+    as much as possible.
+
+    INPUT PARAMETERS:
+        M       -   number of rows in A
+        N       -   number of variables, N>0
+
+    OUTPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 14.11.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static void linlsqrcreatebuf(int m, int n, linlsqrstate state)
+    {
+    
+        linlsqr.linlsqrcreatebuf(m, n, state.innerobj, null);
+    }
+    
+    public static void linlsqrcreatebuf(int m, int n, linlsqrstate state, alglib.xparams _params)
+    {
+    
+        linlsqr.linlsqrcreatebuf(m, n, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function  changes  preconditioning  settings of LinLSQQSolveSparse()
+    function. By default, SolveSparse() uses diagonal preconditioner,  but  if
+    you want to use solver without preconditioning, you can call this function
+    which forces solver to use unit matrix for preconditioning.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 19.11.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void linlsqrsetprecunit(linlsqrstate state)
+    {
+    
+        linlsqr.linlsqrsetprecunit(state.innerobj, null);
+    }
+    
+    public static void linlsqrsetprecunit(linlsqrstate state, alglib.xparams _params)
+    {
+    
+        linlsqr.linlsqrsetprecunit(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function  changes  preconditioning  settings  of  LinCGSolveSparse()
+    function.  LinCGSolveSparse() will use diagonal of the  system  matrix  as
+    preconditioner. This preconditioning mode is active by default.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 19.11.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void linlsqrsetprecdiag(linlsqrstate state)
+    {
+    
+        linlsqr.linlsqrsetprecdiag(state.innerobj, null);
+    }
+    
+    public static void linlsqrsetprecdiag(linlsqrstate state, alglib.xparams _params)
+    {
+    
+        linlsqr.linlsqrsetprecdiag(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void linlsqrsetlambdai(linlsqrstate state, double lambdai, alglib.xparams _params)
+    {
+    
+        linlsqr.linlsqrsetlambdai(state.innerobj, lambdai, _params);
+    }
+    
+    /*************************************************************************
+    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()
+
+    NOTE: this function uses lightweight preconditioning -  multiplication  by
+          inverse of diag(A). If you want, you can turn preconditioning off by
+          calling LinLSQRSetPrecUnit(). However, preconditioning cost is   low
+          and preconditioner is very important for solution  of  badly  scaled
+          problems.
+
+      -- 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, null);
+    }
+    
+    public static void linlsqrsolvesparse(linlsqrstate state, sparsematrix a, double[] b, alglib.xparams _params)
+    {
+    
+        linlsqr.linlsqrsolvesparse(state.innerobj, a.innerobj, b, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void linlsqrsetcond(linlsqrstate state, double epsa, double epsb, int maxits, alglib.xparams _params)
+    {
+    
+        linlsqr.linlsqrsetcond(state.innerobj, epsa, epsb, maxits, _params);
+    }
+    
+    /*************************************************************************
+    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)
+                        *  8    user requested termination via calling
+                                linlsqrrequesttermination()
+                    * 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, null);
+    }
+    
+    public static void linlsqrresults(linlsqrstate state, out double[] x, out linlsqrreport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new linlsqrreport();
+        linlsqr.linlsqrresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void linlsqrsetxrep(linlsqrstate state, bool needxrep, alglib.xparams _params)
+    {
+    
+        linlsqr.linlsqrsetxrep(state.innerobj, needxrep, _params);
+    }
+    
+    /*************************************************************************
+    This function is used to peek into LSQR solver and get  current  iteration
+    counter. You can safely "peek" into the solver from another thread.
+
+    INPUT PARAMETERS:
+        S           -   solver object
+
+    RESULT:
+        iteration counter, in [0,INF)
+
+      -- ALGLIB --
+         Copyright 21.05.2018 by Bochkanov Sergey
+    *************************************************************************/
+    public static int linlsqrpeekiterationscount(linlsqrstate s)
+    {
+    
+        return linlsqr.linlsqrpeekiterationscount(s.innerobj, null);
+    }
+    
+    public static int linlsqrpeekiterationscount(linlsqrstate s, alglib.xparams _params)
+    {
+    
+        return linlsqr.linlsqrpeekiterationscount(s.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This subroutine submits request for termination of the running solver.  It
+    can be called from some other thread which wants LSQR solver to  terminate
+    (obviously, the  thread  running  LSQR  solver can not request termination
+    because it is already busy working on LSQR).
+
+    As result, solver  stops  at  point  which  was  "current  accepted"  when
+    termination  request  was  submitted  and returns error code 8 (successful
+    termination).  Such   termination   is  a smooth  process  which  properly
+    deallocates all temporaries.
+
+    INPUT PARAMETERS:
+        State   -   solver structure
+
+    NOTE: calling this function on solver which is NOT running  will  have  no
+          effect.
+
+    NOTE: multiple calls to this function are possible. First call is counted,
+          subsequent calls are silently ignored.
+
+    NOTE: solver clears termination flag on its start, it means that  if  some
+          other thread will request termination too soon, its request will went
+          unnoticed.
+
+      -- ALGLIB --
+         Copyright 08.10.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void linlsqrrequesttermination(linlsqrstate state)
+    {
+    
+        linlsqr.linlsqrrequesttermination(state.innerobj, null);
+    }
+    
+    public static void linlsqrrequesttermination(linlsqrstate state, alglib.xparams _params)
+    {
+    
+        linlsqr.linlsqrrequesttermination(state.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class polynomialsolverreport : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public double maxerr { get { return _innerobj.maxerr; } set { _innerobj.maxerr = value; } }
+    
+        public polynomialsolverreport()
+        {
+            _innerobj = new polynomialsolver.polynomialsolverreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new polynomialsolverreport((polynomialsolver.polynomialsolverreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private polynomialsolver.polynomialsolverreport _innerobj;
+        public polynomialsolver.polynomialsolverreport innerobj { get { return _innerobj; } }
+        public polynomialsolverreport(polynomialsolver.polynomialsolverreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+    
+    /*************************************************************************
+    Polynomial root finding.
+
+    This function returns all roots of the polynomial
+        P(x) = a0 + a1*x + a2*x^2 + ... + an*x^n
+    Both real and complex roots are returned (see below).
+
+    INPUT PARAMETERS:
+        A       -   array[N+1], polynomial coefficients:
+                    * A[0] is constant term
+                    * A[N] is a coefficient of X^N
+        N       -   polynomial degree
+
+    OUTPUT PARAMETERS:
+        X       -   array of complex roots:
+                    * for isolated real root, X[I] is strictly real: IMAGE(X[I])=0
+                    * complex roots are always returned in pairs - roots occupy
+                      positions I and I+1, with:
+                      * X[I+1]=Conj(X[I])
+                      * IMAGE(X[I]) > 0
+                      * IMAGE(X[I+1]) = -IMAGE(X[I]) < 0
+                    * multiple real roots may have non-zero imaginary part due
+                      to roundoff errors. There is no reliable way to distinguish
+                      real root of multiplicity 2 from two  complex  roots  in
+                      the presence of roundoff errors.
+        Rep     -   report, additional information, following fields are set:
+                    * Rep.MaxErr - max( |P(xi)| )  for  i=0..N-1.  This  field
+                      allows to quickly estimate "quality" of the roots  being
+                      returned.
+
+    NOTE:   this function uses companion matrix method to find roots. In  case
+            internal EVD  solver  fails  do  find  eigenvalues,  exception  is
+            generated.
+
+    NOTE:   roots are not "polished" and  no  matrix  balancing  is  performed
+            for them.
+
+      -- ALGLIB --
+         Copyright 24.02.2014 by Bochkanov Sergey
+    *************************************************************************/
+    public static void polynomialsolve(double[] a, int n, out complex[] x, out polynomialsolverreport rep)
+    {
+        x = new complex[0];
+        rep = new polynomialsolverreport();
+        polynomialsolver.polynomialsolve(a, n, ref x, rep.innerobj, null);
+    }
+    
+    public static void polynomialsolve(double[] a, int n, out complex[] x, out polynomialsolverreport rep, alglib.xparams _params)
+    {
+        x = new complex[0];
+        rep = new polynomialsolverreport();
+        polynomialsolver.polynomialsolve(a, n, ref x, rep.innerobj, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+
+    *************************************************************************/
+    public class nleqstate : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new nleqstate((nleq.nleqstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new nleqreport((nleq.nleqreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void nleqcreatelm(int n, int m, double[] x, out nleqstate state, alglib.xparams _params)
+    {
+        state = new nleqstate();
+        nleq.nleqcreatelm(n, m, x, state.innerobj, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void nleqcreatelm(int m, double[] x, out nleqstate state, alglib.xparams _params)
+    {
+        int n;
+    
+        state = new nleqstate();
+        n = ap.len(x);
+        nleq.nleqcreatelm(n, m, x, state.innerobj, _params);
+    
+        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, null);
+    }
+    
+    public static void nleqsetcond(nleqstate state, double epsf, int maxits, alglib.xparams _params)
+    {
+    
+        nleq.nleqsetcond(state.innerobj, epsf, maxits, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void nleqsetxrep(nleqstate state, bool needxrep, alglib.xparams _params)
+    {
+    
+        nleq.nleqsetxrep(state.innerobj, needxrep, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void nleqsetstpmax(nleqstate state, double stpmax, alglib.xparams _params)
+    {
+    
+        nleq.nleqsetstpmax(state.innerobj, stpmax, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return nleq.nleqiteration(state.innerobj, null);
+    }
+    
+    public static bool nleqiteration(nleqstate state, alglib.xparams _params)
+    {
+    
+        return nleq.nleqiteration(state.innerobj, _params);
+    }
+    /*************************************************************************
+    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)
+    {
+        nleqsolve(state, func, jac, rep, obj, null);
+    }
+    
+    public static void nleqsolve(nleqstate state, ndimensional_func func, ndimensional_jac  jac, ndimensional_rep rep, object obj, alglib.xparams _params)
+    {
+        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, _params) )
+        {
+            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, null);
+    }
+    
+    public static void nleqresults(nleqstate state, out double[] x, out nleqreport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new nleqreport();
+        nleq.nleqresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void nleqresultsbuf(nleqstate state, ref double[] x, nleqreport rep, alglib.xparams _params)
+    {
+    
+        nleq.nleqresultsbuf(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void nleqrestartfrom(nleqstate state, double[] x, alglib.xparams _params)
+    {
+    
+        nleq.nleqrestartfrom(state.innerobj, x, _params);
+    }
+
+}
+public partial class alglib
+{
+
+
+    /*************************************************************************
+    This structure is a sparse solver report.
+
+    Following fields can be accessed by users:
+    *************************************************************************/
+    public class sparsesolverreport : alglibobject
+    {
+        //
+        // Public declarations
+        //
+        public int terminationtype { get { return _innerobj.terminationtype; } set { _innerobj.terminationtype = value; } }
+    
+        public sparsesolverreport()
+        {
+            _innerobj = new directsparsesolvers.sparsesolverreport();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new sparsesolverreport((directsparsesolvers.sparsesolverreport)_innerobj.make_copy());
+        }
+    
+        //
+        // Although some of declarations below are public, you should not use them
+        // They are intended for internal use only
+        //
+        private directsparsesolvers.sparsesolverreport _innerobj;
+        public directsparsesolvers.sparsesolverreport innerobj { get { return _innerobj; } }
+        public sparsesolverreport(directsparsesolvers.sparsesolverreport obj)
+        {
+            _innerobj = obj;
+        }
+    }
+    
+    /*************************************************************************
+    Sparse linear solver for A*x=b with N*N  sparse  real  symmetric  positive
+    definite matrix A, N*1 vectors x and b.
+
+    This solver  converts  input  matrix  to  SKS  format,  performs  Cholesky
+    factorization using  SKS  Cholesky  subroutine  (works  well  for  limited
+    bandwidth matrices) and uses sparse triangular solvers to get solution  of
+    the original system.
+
+    INPUT PARAMETERS
+        A       -   sparse matrix, must be NxN exactly
+        N       -   size of A, N>0
+        IsUpper -   which half of A is provided (another half is ignored)
+        B       -   array[0..N-1], right part
+
+    OUTPUT PARAMETERS
+        Rep     -   solver report, following fields are set:
+                    * rep.terminationtype - solver status; >0 for success,
+                      set to -3 on failure (degenerate or non-SPD system).
+        X       -   array[N], it contains:
+                    * rep.terminationtype>0    =>  solution
+                    * rep.terminationtype=-3   =>  filled by zeros
+
+      -- ALGLIB --
+         Copyright 26.12.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsesolvesks(sparsematrix a, int n, bool isupper, double[] b, out sparsesolverreport rep, out double[] x)
+    {
+        rep = new sparsesolverreport();
+        x = new double[0];
+        directsparsesolvers.sparsesolvesks(a.innerobj, n, isupper, b, rep.innerobj, ref x, null);
+    }
+    
+    public static void sparsesolvesks(sparsematrix a, int n, bool isupper, double[] b, out sparsesolverreport rep, out double[] x, alglib.xparams _params)
+    {
+        rep = new sparsesolverreport();
+        x = new double[0];
+        directsparsesolvers.sparsesolvesks(a.innerobj, n, isupper, b, rep.innerobj, ref x, _params);
+    }
+    
+    /*************************************************************************
+    Sparse linear solver for A*x=b with N*N real  symmetric  positive definite
+    matrix A given by its Cholesky decomposition, and N*1 vectors x and b.
+
+    IMPORTANT: this solver requires input matrix to be in  the  SKS  (Skyline)
+               sparse storage format. An exception will be  generated  if  you
+               pass matrix in some other format (HASH or CRS).
+
+    INPUT PARAMETERS
+        A       -   sparse NxN matrix stored in SKS format, must be NxN exactly
+        N       -   size of A, N>0
+        IsUpper -   which half of A is provided (another half is ignored)
+        B       -   array[N], right part
+
+    OUTPUT PARAMETERS
+        Rep     -   solver report, following fields are set:
+                    * rep.terminationtype - solver status; >0 for success,
+                      set to -3 on failure (degenerate or non-SPD system).
+        X       -   array[N], it contains:
+                    * rep.terminationtype>0    =>  solution
+                    * rep.terminationtype=-3   =>  filled by zeros
+
+      -- ALGLIB --
+         Copyright 26.12.2017 by Bochkanov Sergey
+    *************************************************************************/
+    public static void sparsecholeskysolvesks(sparsematrix a, int n, bool isupper, double[] b, out sparsesolverreport rep, out double[] x)
+    {
+        rep = new sparsesolverreport();
+        x = new double[0];
+        directsparsesolvers.sparsecholeskysolvesks(a.innerobj, n, isupper, b, rep.innerobj, ref x, null);
+    }
+    
+    public static void sparsecholeskysolvesks(sparsematrix a, int n, bool isupper, double[] b, out sparsesolverreport rep, out double[] x, alglib.xparams _params)
+    {
+        rep = new sparsesolverreport();
+        x = new double[0];
+        directsparsesolvers.sparsecholeskysolvesks(a.innerobj, n, isupper, b, rep.innerobj, ref x, _params);
+    }
+
+}
+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 : alglibobject
+    {
+        //
+        // Public declarations
+        //
+    
+        public lincgstate()
+        {
+            _innerobj = new lincg.lincgstate();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new lincgstate((lincg.lincgstate)_innerobj.make_copy());
+        }
+    
+        //
+        // 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 : alglibobject
+    {
+        //
+        // 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();
+        }
+        
+        public override alglib.alglibobject make_copy()
+        {
+            return new lincgreport((lincg.lincgreport)_innerobj.make_copy());
+        }
+    
+        //
+        // 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, null);
+    }
+    
+    public static void lincgcreate(int n, out lincgstate state, alglib.xparams _params)
+    {
+        state = new lincgstate();
+        lincg.lincgcreate(n, state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lincgsetstartingpoint(lincgstate state, double[] x, alglib.xparams _params)
+    {
+    
+        lincg.lincgsetstartingpoint(state.innerobj, x, _params);
+    }
+    
+    /*************************************************************************
+    This  function  changes  preconditioning  settings  of  LinCGSolveSparse()
+    function. By default, SolveSparse() uses diagonal preconditioner,  but  if
+    you want to use solver without preconditioning, you can call this function
+    which forces solver to use unit matrix for preconditioning.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 19.11.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lincgsetprecunit(lincgstate state)
+    {
+    
+        lincg.lincgsetprecunit(state.innerobj, null);
+    }
+    
+    public static void lincgsetprecunit(lincgstate state, alglib.xparams _params)
+    {
+    
+        lincg.lincgsetprecunit(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    This  function  changes  preconditioning  settings  of  LinCGSolveSparse()
+    function.  LinCGSolveSparse() will use diagonal of the  system  matrix  as
+    preconditioner. This preconditioning mode is active by default.
+
+    INPUT PARAMETERS:
+        State   -   structure which stores algorithm state
+
+      -- ALGLIB --
+         Copyright 19.11.2012 by Bochkanov Sergey
+    *************************************************************************/
+    public static void lincgsetprecdiag(lincgstate state)
+    {
+    
+        lincg.lincgsetprecdiag(state.innerobj, null);
+    }
+    
+    public static void lincgsetprecdiag(lincgstate state, alglib.xparams _params)
+    {
+    
+        lincg.lincgsetprecdiag(state.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lincgsetcond(lincgstate state, double epsf, int maxits, alglib.xparams _params)
+    {
+    
+        lincg.lincgsetcond(state.innerobj, epsf, maxits, _params);
+    }
+    
+    /*************************************************************************
+    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()
+
+    NOTE: this function uses lightweight preconditioning -  multiplication  by
+          inverse of diag(A). If you want, you can turn preconditioning off by
+          calling LinCGSetPrecUnit(). However, preconditioning cost is low and
+          preconditioner  is  very  important  for  solution  of  badly scaled
+          problems.
+
+      -- 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, null);
+    }
+    
+    public static void lincgsolvesparse(lincgstate state, sparsematrix a, bool isupper, double[] b, alglib.xparams _params)
+    {
+    
+        lincg.lincgsolvesparse(state.innerobj, a.innerobj, isupper, b, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lincgresults(lincgstate state, out double[] x, out lincgreport rep, alglib.xparams _params)
+    {
+        x = new double[0];
+        rep = new lincgreport();
+        lincg.lincgresults(state.innerobj, ref x, rep.innerobj, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lincgsetrestartfreq(lincgstate state, int srf, alglib.xparams _params)
+    {
+    
+        lincg.lincgsetrestartfreq(state.innerobj, srf, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lincgsetrupdatefreq(lincgstate state, int freq, alglib.xparams _params)
+    {
+    
+        lincg.lincgsetrupdatefreq(state.innerobj, freq, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void lincgsetxrep(lincgstate state, bool needxrep, alglib.xparams _params)
+    {
+    
+        lincg.lincgsetxrep(state.innerobj, needxrep, _params);
+    }
+
+}
+public partial class alglib
+{
+    public class directdensesolvers
+    {
+        public class densesolverreport : apobject
+        {
+            public double r1;
+            public double rinf;
+            public densesolverreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                densesolverreport _result = new densesolverreport();
+                _result.r1 = r1;
+                _result.rinf = rinf;
+                return _result;
+            }
+        };
+
+
+        public class densesolverlsreport : apobject
+        {
+            public double r2;
+            public double[,] cx;
+            public int n;
+            public int k;
+            public densesolverlsreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+                cx = new double[0,0];
+            }
+            public override alglib.apobject make_copy()
+            {
+                densesolverlsreport _result = new densesolverlsreport();
+                _result.r2 = r2;
+                _result.cx = (double[,])cx.Clone();
+                _result.n = n;
+                _result.k = k;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        Dense solver for A*x=b with N*N real matrix A and N*1 real vectorx  x  and
+        b. This is "slow-but-feature rich" version of the  linear  solver.  Faster
+        version is RMatrixSolveFast() function.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * iterative refinement
+        * O(N^3) complexity
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear  system
+                   ! and  performs  iterative   refinement,   which   results   in
+                   ! significant performance penalty  when  compared  with  "fast"
+                   ! version  which  just  performs  LU  decomposition  and  calls
+                   ! triangular solver.
+                   !
+                   ! This  performance  penalty  is  especially  visible  in   the
+                   ! multithreaded mode, because both condition number  estimation
+                   ! and   iterative    refinement   are   inherently   sequential
+                   ! calculations. It is also very significant on small matrices.
+                   !
+                   ! Thus, if you need high performance and if you are pretty sure
+                   ! that your system is well conditioned, we  strongly  recommend
+                   ! you to use faster solver, RMatrixSolveFast() function.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    matrix is very badly conditioned or exactly singular.
+                        * -1    N<=0 was passed
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            x = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver.
+
+        This  subroutine  solves  a  system  A*x=b,  where A is NxN non-denegerate
+        real matrix, x  and  b  are  vectors.  This is a "fast" version of  linear
+        solver which does NOT provide  any  additional  functions  like  condition
+        number estimation or iterative refinement.
+
+        Algorithm features:
+        * efficient algorithm O(N^3) complexity
+        * no performance overhead from additional functionality
+
+        If you need condition number estimation or iterative refinement, use  more
+        feature-rich version - RMatrixSolve().
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    matrix is exactly singular (ill conditioned matrices
+                                are not recognized).
+                        * -1    N<=0 was passed
+                        *  1    task is solved 
+            B       -   array[N]:
+                        * info>0    =>  overwritten by solution
+                        * info=-3   =>  filled by zeros
+
+          -- ALGLIB --
+             Copyright 16.03.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixsolvefast(double[,] a,
+            int n,
+            double[] b,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int[] p = new int[0];
+
+            a = (double[,])a.Clone();
+            info = 0;
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            trfac.rmatrixlu(ref a, n, n, ref p, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(a[i,i])==(double)(0) )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        b[j] = 0.0;
+                    }
+                    info = -3;
+                    return;
+                }
+            }
+            rbasiclusolve(a, p, n, b, _params);
+            info = 1;
+        }
+
+
+        /*************************************************************************
+        Dense solver.
+
+        Similar to RMatrixSolve() but solves task with multiple right parts (where
+        b and x are NxM matrices). This is  "slow-but-robust"  version  of  linear
+        solver with additional functionality  like  condition  number  estimation.
+        There also exists faster version - RMatrixSolveMFast().
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * optional iterative refinement
+        * O(N^3+M*N^2) complexity
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear  system
+                   ! and  performs  iterative   refinement,   which   results   in
+                   ! significant performance penalty  when  compared  with  "fast"
+                   ! version  which  just  performs  LU  decomposition  and  calls
+                   ! triangular solver.
+                   !
+                   ! This  performance  penalty  is  especially  visible  in   the
+                   ! multithreaded mode, because both condition number  estimation
+                   ! and   iterative    refinement   are   inherently   sequential
+                   ! calculations. It also very significant on small matrices.
+                   !
+                   ! Thus, if you need high performance and if you are pretty sure
+                   ! that your system is well conditioned, we  strongly  recommend
+                   ! you to use faster solver, RMatrixSolveMFast() function.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    -   return code:
+                        * -3    A is ill conditioned or 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     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            double[,] da = new double[0,0];
+            double[,] emptya = new double[0,0];
+            int[] p = new int[0];
+            int i = 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. factorize matrix
+            // 3. solve
+            //
+            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, _params);
+            if( rfs )
+            {
+                rmatrixlusolveinternal(da, p, n, a, true, b, m, ref info, rep, ref x, _params);
+            }
+            else
+            {
+                rmatrixlusolveinternal(da, p, n, emptya, false, b, m, ref info, rep, ref x, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver.
+
+        Similar to RMatrixSolve() but solves task with multiple right parts (where
+        b and x are NxM matrices). This is "fast" version of linear  solver  which
+        does NOT offer additional functions like condition  number  estimation  or
+        iterative refinement.
+
+        Algorithm features:
+        * O(N^3+M*N^2) complexity
+        * no additional functionality, highest performance
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    -   return code:
+                        * -3    matrix is exactly singular (ill conditioned matrices
+                                are not recognized).
+                                X is filled by zeros in such cases.
+                        * -1    N<=0 was passed
+                        *  1    task is solved
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            B       -   array[N]:
+                        * info>0    =>  overwritten by solution
+                        * info=-3   =>  filled by zeros
+
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixsolvemfast(double[,] a,
+            int n,
+            double[,] b,
+            int m,
+            ref int info,
+            alglib.xparams _params)
+        {
+            double v = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int[] p = new int[0];
+
+            a = (double[,])a.Clone();
+            info = 0;
+
+            
+            //
+            // Check for exact degeneracy
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            trfac.rmatrixlu(ref a, n, n, ref p, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(a[i,i])==(double)(0) )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        for(k=0; k<=m-1; k++)
+                        {
+                            b[j,k] = 0.0;
+                        }
+                    }
+                    info = -3;
+                    return;
+                }
+            }
+            
+            //
+            // Solve with TRSM()
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( p[i]!=i )
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        v = b[i,j];
+                        b[i,j] = b[p[i],j];
+                        b[p[i],j] = v;
+                    }
+                }
+            }
+            ablas.rmatrixlefttrsm(n, m, a, 0, 0, false, true, 0, b, 0, 0, _params);
+            ablas.rmatrixlefttrsm(n, m, a, 0, 0, true, false, 0, b, 0, 0, _params);
+            info = 1;
+        }
+
+
+        /*************************************************************************
+        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 real vectors.  This
+        is "slow-but-robust" version of the linear LU-based solver. Faster version
+        is RMatrixLUSolveFast() function.
+
+        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.
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear system,
+                   ! which results in 10-15x  performance  penalty  when  compared
+                   ! with "fast" version which just calls triangular solver.
+                   !
+                   ! This performance penalty is insignificant  when compared with
+                   ! cost of large LU decomposition.  However,  if you  call  this
+                   ! function many times for the same  left  side,  this  overhead
+                   ! BECOMES significant. It  also  becomes significant for small-
+                   ! scale problems.
+                   !
+                   ! In such cases we strongly recommend you to use faster solver,
+                   ! RMatrixLUSolveFast() function.
+
+        INPUT PARAMETERS
+            LUA     -   array[N,N], LU decomposition, RMatrixLU result
+            P       -   array[N], pivots array, RMatrixLU result
+            N       -   size of A
+            B       -   array[N], right part
+
+        OUTPUT PARAMETERS
+            Info    -   return code:
+                        * -3    matrix is very badly conditioned or exactly singular.
+                        * -1    N<=0 was passed
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+            
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            x = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        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 real vectors.  This
+        is "fast-without-any-checks" version of the linear LU-based solver. Slower
+        but more robust version is RMatrixLUSolve() function.
+
+        Algorithm features:
+        * O(N^2) complexity
+        * fast algorithm without ANY additional checks, just triangular solver
+
+        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    -   return code:
+                        * -3    matrix is exactly singular (ill conditioned matrices
+                                are not recognized).
+                                X is filled by zeros in such cases.
+                        * -1    N<=0 was passed
+                        *  1    task is solved 
+            B       -   array[N]:
+                        * info>0    =>  overwritten by solution
+                        * info=-3   =>  filled by zeros
+
+          -- ALGLIB --
+             Copyright 18.03.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixlusolvefast(double[,] lua,
+            int[] p,
+            int n,
+            double[] b,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            info = 0;
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(lua[i,i])==(double)(0) )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        b[j] = 0.0;
+                    }
+                    info = -3;
+                    return;
+                }
+            }
+            rbasiclusolve(lua, p, n, b, _params);
+            info = 1;
+        }
+
+
+        /*************************************************************************
+        Dense solver.
+
+        Similar to RMatrixLUSolve() but solves  task  with  multiple  right  parts
+        (where b and x are NxM matrices). This  is  "robust-but-slow"  version  of
+        LU-based solver which performs additional  checks  for  non-degeneracy  of
+        inputs (condition number estimation). If you need  best  performance,  use
+        "fast-without-any-checks" version, RMatrixLUSolveMFast().
+
+        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.
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear system,
+                   ! which  results  in  significant  performance   penalty   when
+                   ! compared with "fast"  version  which  just  calls  triangular
+                   ! solver.
+                   !
+                   ! This performance penalty is especially apparent when you  use
+                   ! ALGLIB parallel capabilities (condition number estimation  is
+                   ! inherently  sequential).  It   also   becomes significant for
+                   ! small-scale problems.
+                   !
+                   ! In such cases we strongly recommend you to use faster solver,
+                   ! RMatrixLUSolveMFast() function.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+          
+        INPUT PARAMETERS
+            LUA     -   array[N,N], LU decomposition, RMatrixLU result
+            P       -   array[N], 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    -   return code:
+                        * -3    matrix is very badly conditioned or exactly 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     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N,M], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            double[,] emptya = new double[0,0];
+
+            info = 0;
+            x = new double[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // solve
+            //
+            rmatrixlusolveinternal(lua, p, n, emptya, false, b, m, ref info, rep, ref x, _params);
+        }
+
+
+        /*************************************************************************
+        Dense solver.
+
+        Similar to RMatrixLUSolve() but solves  task  with  multiple  right parts,
+        where b and x are NxM matrices.  This is "fast-without-any-checks" version
+        of LU-based solver. It does not estimate  condition number  of  a  system,
+        so it is extremely fast. If you need better detection  of  near-degenerate
+        cases, use RMatrixLUSolveM() function.
+
+        Algorithm features:
+        * O(M*N^2) complexity
+        * fast algorithm without ANY additional checks, just triangular solver
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    -   return code:
+                        * -3    matrix is exactly singular (ill conditioned matrices
+                                are not recognized).
+                        * -1    N<=0 was passed
+                        *  1    task is solved
+            B       -   array[N,M]:
+                        * info>0    =>  overwritten by solution
+                        * info=-3   =>  filled by zeros
+
+          -- ALGLIB --
+             Copyright 18.03.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rmatrixlusolvemfast(double[,] lua,
+            int[] p,
+            int n,
+            double[,] b,
+            int m,
+            ref int info,
+            alglib.xparams _params)
+        {
+            double v = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+
+            info = 0;
+
+            
+            //
+            // Check for exact degeneracy
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(lua[i,i])==(double)(0) )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        for(k=0; k<=m-1; k++)
+                        {
+                            b[j,k] = 0.0;
+                        }
+                    }
+                    info = -3;
+                    return;
+                }
+            }
+            
+            //
+            // Solve with TRSM()
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( p[i]!=i )
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        v = b[i,j];
+                        b[i,j] = b[p[i],j];
+                        b[p[i],j] = v;
+                    }
+                }
+            }
+            ablas.rmatrixlefttrsm(n, m, lua, 0, 0, false, true, 0, b, 0, 0, _params);
+            ablas.rmatrixlefttrsm(n, m, lua, 0, 0, true, false, 0, b, 0, 0, _params);
+            info = 1;
+        }
+
+
+        /*************************************************************************
+        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    -   return code:
+                        * -3    matrix is very badly conditioned or exactly singular.
+                        * -1    N<=0 was passed
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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    -   return code:
+                        * -3    matrix is very badly conditioned or exactly singular.
+                        * -1    N<=0 was passed
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N,M], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            info = 0;
+            x = new double[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // solve
+            //
+            rmatrixlusolveinternal(lua, p, n, a, true, b, m, ref info, rep, ref x, _params);
+        }
+
+
+        /*************************************************************************
+        Complex dense solver for A*X=B with N*N  complex  matrix  A,  N*M  complex
+        matrices  X  and  B.  "Slow-but-feature-rich"   version   which   provides
+        additional functions, at the cost of slower  performance.  Faster  version
+        may be invoked with CMatrixSolveMFast() function.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * iterative refinement
+        * O(N^3+M*N^2) complexity
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear  system
+                   ! and  performs  iterative   refinement,   which   results   in
+                   ! significant performance penalty  when  compared  with  "fast"
+                   ! version  which  just  performs  LU  decomposition  and  calls
+                   ! triangular solver.
+                   !
+                   ! This  performance  penalty  is  especially  visible  in   the
+                   ! multithreaded mode, because both condition number  estimation
+                   ! and   iterative    refinement   are   inherently   sequential
+                   ! calculations.
+                   !
+                   ! Thus, if you need high performance and if you are pretty sure
+                   ! that your system is well conditioned, we  strongly  recommend
+                   ! you to use faster solver, CMatrixSolveMFast() function.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    -   return code:
+                        * -3    matrix is very badly conditioned or exactly 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     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N,M], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            complex[,] da = new complex[0,0];
+            complex[,] emptya = new complex[0,0];
+            int[] p = new int[0];
+            int i = 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];
+            
+            //
+            // factorize, solve
+            //
+            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, _params);
+            if( rfs )
+            {
+                cmatrixlusolveinternal(da, p, n, a, true, b, m, ref info, rep, ref x, _params);
+            }
+            else
+            {
+                cmatrixlusolveinternal(da, p, n, emptya, false, b, m, ref info, rep, ref x, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Complex dense solver for A*X=B with N*N  complex  matrix  A,  N*M  complex
+        matrices  X  and  B.  "Fast-but-lightweight" version which  provides  just
+        triangular solver - and no additional functions like iterative  refinement
+        or condition number estimation.
+
+        Algorithm features:
+        * O(N^3+M*N^2) complexity
+        * no additional time consuming functions
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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
+
+        OUTPUT PARAMETERS:
+            Info    -   return code:
+                        * -3    matrix is exactly singular (ill conditioned matrices
+                                are not recognized).
+                        * -1    N<=0 was passed
+                        *  1    task is solved 
+            B       -   array[N,M]:
+                        * info>0    =>  overwritten by solution
+                        * info=-3   =>  filled by zeros
+
+          -- ALGLIB --
+             Copyright 16.03.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixsolvemfast(complex[,] a,
+            int n,
+            complex[,] b,
+            int m,
+            ref int info,
+            alglib.xparams _params)
+        {
+            complex v = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+            int[] p = new int[0];
+
+            a = (complex[,])a.Clone();
+            info = 0;
+
+            
+            //
+            // Check for exact degeneracy
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            trfac.cmatrixlu(ref a, n, n, ref p, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( a[i,i]==0 )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        for(k=0; k<=m-1; k++)
+                        {
+                            b[j,k] = 0.0;
+                        }
+                    }
+                    info = -3;
+                    return;
+                }
+            }
+            
+            //
+            // Solve with TRSM()
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( p[i]!=i )
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        v = b[i,j];
+                        b[i,j] = b[p[i],j];
+                        b[p[i],j] = v;
+                    }
+                }
+            }
+            ablas.cmatrixlefttrsm(n, m, a, 0, 0, false, true, 0, b, 0, 0, _params);
+            ablas.cmatrixlefttrsm(n, m, a, 0, 0, true, false, 0, b, 0, 0, _params);
+            info = 1;
+        }
+
+
+        /*************************************************************************
+        Complex dense solver for A*x=B with N*N complex matrix A and  N*1  complex
+        vectors x and b. "Slow-but-feature-rich" version of the solver.
+
+        Algorithm features:
+        * automatic detection of degenerate cases
+        * condition number estimation
+        * iterative refinement
+        * O(N^3) complexity
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear  system
+                   ! and  performs  iterative   refinement,   which   results   in
+                   ! significant performance penalty  when  compared  with  "fast"
+                   ! version  which  just  performs  LU  decomposition  and  calls
+                   ! triangular solver.
+                   !
+                   ! This  performance  penalty  is  especially  visible  in   the
+                   ! multithreaded mode, because both condition number  estimation
+                   ! and   iterative    refinement   are   inherently   sequential
+                   ! calculations.
+                   !
+                   ! Thus, if you need high performance and if you are pretty sure
+                   ! that your system is well conditioned, we  strongly  recommend
+                   ! you to use faster solver, CMatrixSolveFast() function.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    matrix is very badly conditioned or exactly singular.
+                        * -1    N<=0 was passed
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            x = new complex[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Complex dense solver for A*x=B with N*N complex matrix A and  N*1  complex
+        vectors x and b. "Fast-but-lightweight" version of the solver.
+
+        Algorithm features:
+        * O(N^3) complexity
+        * no additional time consuming features, just triangular solver
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    matrix is exactly singular (ill conditioned matrices
+                                are not recognized).
+                        * -1    N<=0 was passed
+                        *  1    task is solved 
+            B       -   array[N]:
+                        * info>0    =>  overwritten by solution
+                        * info=-3   =>  filled by zeros
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixsolvefast(complex[,] a,
+            int n,
+            complex[] b,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int[] p = new int[0];
+
+            a = (complex[,])a.Clone();
+            info = 0;
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            trfac.cmatrixlu(ref a, n, n, ref p, _params);
+            for(i=0; i<=n-1; i++)
+            {
+                if( a[i,i]==0 )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        b[j] = 0.0;
+                    }
+                    info = -3;
+                    return;
+                }
+            }
+            cbasiclusolve(a, p, n, b, _params);
+            info = 1;
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*X=B with N*N complex A given by its  LU  decomposition,
+        and N*M matrices X and B (multiple right sides).   "Slow-but-feature-rich"
+        version of the solver.
+
+        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.
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear system,
+                   ! which  results  in  significant  performance   penalty   when
+                   ! compared with "fast"  version  which  just  calls  triangular
+                   ! solver.
+                   !
+                   ! This performance penalty is especially apparent when you  use
+                   ! ALGLIB parallel capabilities (condition number estimation  is
+                   ! inherently  sequential).  It   also   becomes significant for
+                   ! small-scale problems.
+                   !
+                   ! In such cases we strongly recommend you to use faster solver,
+                   ! CMatrixLUSolveMFast() function.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    -   return code:
+                        * -3    matrix is very badly conditioned or exactly singular.
+                        * -1    N<=0 was passed
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N,M], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            complex[,] emptya = new complex[0,0];
+
+            info = 0;
+            x = new complex[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // solve
+            //
+            cmatrixlusolveinternal(lua, p, n, emptya, false, b, m, ref info, rep, ref x, _params);
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*X=B with N*N complex A given by its  LU  decomposition,
+        and N*M matrices X and B (multiple  right  sides).  "Fast-but-lightweight"
+        version of the solver.
+
+        Algorithm features:
+        * O(M*N^2) complexity
+        * no additional time-consuming features
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    -   return code:
+                        * -3    matrix is exactly singular (ill conditioned matrices
+                                are not recognized).
+                        * -1    N<=0 was passed
+                        *  1    task is solved 
+            B       -   array[N,M]:
+                        * info>0    =>  overwritten by solution
+                        * info=-3   =>  filled by zeros
+
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixlusolvemfast(complex[,] lua,
+            int[] p,
+            int n,
+            complex[,] b,
+            int m,
+            ref int info,
+            alglib.xparams _params)
+        {
+            complex v = 0;
+            int i = 0;
+            int j = 0;
+            int k = 0;
+
+            info = 0;
+
+            
+            //
+            // Check for exact degeneracy
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( lua[i,i]==0 )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        for(k=0; k<=m-1; k++)
+                        {
+                            b[j,k] = 0.0;
+                        }
+                    }
+                    info = -3;
+                    return;
+                }
+            }
+            
+            //
+            // Solve with TRSM()
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                if( p[i]!=i )
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        v = b[i,j];
+                        b[i,j] = b[p[i],j];
+                        b[p[i],j] = v;
+                    }
+                }
+            }
+            ablas.cmatrixlefttrsm(n, m, lua, 0, 0, false, true, 0, b, 0, 0, _params);
+            ablas.cmatrixlefttrsm(n, m, lua, 0, 0, true, false, 0, b, 0, 0, _params);
+            info = 1;
+        }
+
+
+        /*************************************************************************
+        Complex dense linear solver for A*x=b with complex N*N A  given  by its LU
+        decomposition and N*1 vectors x and b. This is  "slow-but-robust"  version
+        of  the  complex  linear  solver  with  additional  features   which   add
+        significant performance overhead. Faster version  is  CMatrixLUSolveFast()
+        function.
+
+        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.
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear system,
+                   ! which results in 10-15x  performance  penalty  when  compared
+                   ! with "fast" version which just calls triangular solver.
+                   !
+                   ! This performance penalty is insignificant  when compared with
+                   ! cost of large LU decomposition.  However,  if you  call  this
+                   ! function many times for the same  left  side,  this  overhead
+                   ! BECOMES significant. It  also  becomes significant for small-
+                   ! scale problems.
+                   !
+                   ! In such cases we strongly recommend you to use faster solver,
+                   ! CMatrixLUSolveFast() function.
+
+        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    -   return code:
+                        * -3    matrix is very badly conditioned or exactly singular.
+                        * -1    N<=0 was passed
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            x = new complex[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Complex dense linear solver for A*x=b with N*N complex A given by  its  LU
+        decomposition and N*1 vectors x and b. This is  fast  lightweight  version
+        of solver, which is significantly faster than CMatrixLUSolve(),  but  does
+        not provide additional information (like condition numbers).
+
+        Algorithm features:
+        * O(N^2) complexity
+        * no additional time-consuming features, just triangular solver
+
+        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    -   return code:
+                        * -3    matrix is exactly singular (ill conditioned matrices
+                                are not recognized).
+                        * -1    N<=0 was passed
+                        *  1    task is solved 
+            B       -   array[N]:
+                        * info>0    =>  overwritten by solution
+                        * info=-3   =>  filled by zeros
+            
+        NOTE: unlike  CMatrixLUSolve(),  this   function   does   NOT   check  for
+              near-degeneracy of input matrix. It  checks  for  EXACT  degeneracy,
+              because this check is easy to do. However,  very  badly  conditioned
+              matrices may went unnoticed.
+
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void cmatrixlusolvefast(complex[,] lua,
+            int[] p,
+            int n,
+            complex[] b,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            info = 0;
+
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( lua[i,i]==0 )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        b[j] = 0.0;
+                    }
+                    info = -3;
+                    return;
+                }
+            }
+            cbasiclusolve(lua, p, n, b, _params);
+            info = 1;
+        }
+
+
+        /*************************************************************************
+        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    -   return code:
+                        * -3    matrix is very badly conditioned or exactly singular.
+                        * -1    N<=0 was passed
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N,M], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            info = 0;
+            x = new complex[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // solve
+            //
+            cmatrixlusolveinternal(lua, p, n, a, true, b, m, ref info, rep, ref x, _params);
+        }
+
+
+        /*************************************************************************
+        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    -   return code:
+                        * -3    matrix is very badly conditioned or exactly singular.
+                        * -1    N<=0 was passed
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            x = new complex[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*X=B with N*N symmetric positive definite matrix A,  and
+        N*M vectors X and B. It is "slow-but-feature-rich" version of the solver.
+
+        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.
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear system,
+                   ! which  results  in  significant   performance   penalty  when
+                   ! compared with "fast" version  which  just  performs  Cholesky
+                   ! decomposition and calls triangular solver.
+                   !
+                   ! This  performance  penalty  is  especially  visible  in   the
+                   ! multithreaded mode, because both condition number  estimation
+                   ! and   iterative    refinement   are   inherently   sequential
+                   ! calculations.
+                   !
+                   ! Thus, if you need high performance and if you are pretty sure
+                   ! that your system is well conditioned, we  strongly  recommend
+                   ! you to use faster solver, SPDMatrixSolveMFast() function.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    -   return code:
+                        * -3    matrix is very badly conditioned or non-SPD.
+                        * -1    N<=0 was passed
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N,M], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            double[,] da = new double[0,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];
+            
+            //
+            // factorize
+            // solve
+            //
+            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, _params) )
+            {
+                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, n, isupper, a, true, b, m, ref info, rep, ref x, _params);
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*X=B with N*N symmetric positive definite matrix A,  and
+        N*M vectors X and B. It is "fast-but-lightweight" version of the solver.
+
+        Algorithm features:
+        * O(N^3+M*N^2) complexity
+        * matrix is represented by its upper or lower triangle
+        * no additional time consuming features
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    -   return code:
+                        * -3    A is is exactly singular
+                        * -1    N<=0 was passed
+                        *  1    task was solved
+            B       -   array[N,M], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- ALGLIB --
+             Copyright 17.03.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spdmatrixsolvemfast(double[,] a,
+            int n,
+            bool isupper,
+            double[,] b,
+            int m,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            a = (double[,])a.Clone();
+            info = 0;
+
+            info = 1;
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            if( !trfac.spdmatrixcholesky(ref a, n, isupper, _params) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        b[i,j] = 0.0;
+                    }
+                }
+                info = -3;
+                return;
+            }
+            if( isupper )
+            {
+                ablas.rmatrixlefttrsm(n, m, a, 0, 0, true, false, 1, b, 0, 0, _params);
+                ablas.rmatrixlefttrsm(n, m, a, 0, 0, true, false, 0, b, 0, 0, _params);
+            }
+            else
+            {
+                ablas.rmatrixlefttrsm(n, m, a, 0, 0, false, false, 0, b, 0, 0, _params);
+                ablas.rmatrixlefttrsm(n, m, a, 0, 0, false, false, 1, b, 0, 0, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Dense linear solver for A*x=b with N*N real  symmetric  positive  definite
+        matrix A,  N*1 vectors x and b.  "Slow-but-feature-rich"  version  of  the
+        solver.
+
+        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.
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear system,
+                   ! which  results  in  significant   performance   penalty  when
+                   ! compared with "fast" version  which  just  performs  Cholesky
+                   ! decomposition and calls triangular solver.
+                   !
+                   ! This  performance  penalty  is  especially  visible  in   the
+                   ! multithreaded mode, because both condition number  estimation
+                   ! and   iterative    refinement   are   inherently   sequential
+                   ! calculations.
+                   !
+                   ! Thus, if you need high performance and if you are pretty sure
+                   ! that your system is well conditioned, we  strongly  recommend
+                   ! you to use faster solver, SPDMatrixSolveFast() function.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    -   return code:
+                        * -3    matrix is very badly conditioned or non-SPD.
+                        * -1    N<=0 was passed
+                        *  1    task is solved (but matrix A may be ill-conditioned,
+                                check R1/RInf parameters for condition numbers).
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            x = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense linear solver for A*x=b with N*N real  symmetric  positive  definite
+        matrix A,  N*1 vectors x and  b.  "Fast-but-lightweight"  version  of  the
+        solver.
+
+        Algorithm features:
+        * O(N^3) complexity
+        * matrix is represented by its upper or lower triangle
+        * no additional time consuming features like condition number estimation
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    -   return code:
+                        * -3    A is is exactly singular or non-SPD
+                        * -1    N<=0 was passed
+                        *  1    task was solved
+            B       -   array[N], it contains:
+                        * info>0    =>  solution
+                        * info=-3   =>  filled by zeros
+
+          -- ALGLIB --
+             Copyright 17.03.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spdmatrixsolvefast(double[,] a,
+            int n,
+            bool isupper,
+            double[] b,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            a = (double[,])a.Clone();
+            info = 0;
+
+            info = 1;
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            if( !trfac.spdmatrixcholesky(ref a, n, isupper, _params) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    b[i] = 0.0;
+                }
+                info = -3;
+                return;
+            }
+            spdbasiccholeskysolve(a, n, isupper, b, _params);
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*X=B with N*N symmetric positive definite matrix A given
+        by its Cholesky decomposition, and N*M vectors X and B. It  is  "slow-but-
+        feature-rich" version of the solver which estimates  condition  number  of
+        the system.
+
+        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.
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear system,
+                   ! which  results  in  significant  performance   penalty   when
+                   ! compared with "fast"  version  which  just  calls  triangular
+                   ! solver. Amount of  overhead  introduced  depends  on  M  (the
+                   ! larger - the more efficient).
+                   !
+                   ! This performance penalty is insignificant  when compared with
+                   ! cost of large LU decomposition.  However,  if you  call  this
+                   ! function many times for the same  left  side,  this  overhead
+                   ! BECOMES significant. It  also  becomes significant for small-
+                   ! scale problems (N<50).
+                   !
+                   ! In such cases we strongly recommend you to use faster solver,
+                   ! SPDMatrixCholeskySolveMFast() function.
+
+        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    -   return code:
+                        * -3    A is is exactly singular or badly conditioned
+                                X is filled by zeros in such cases.
+                        * -1    N<=0 was passed
+                        *  1    task was solved
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N]:
+                        * for info>0 contains solution
+                        * for info=-3 filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            double[,] emptya = new double[0,0];
+
+            info = 0;
+            x = new double[0,0];
+
+            
+            //
+            // prepare: check inputs, allocate space...
+            //
+            if( n<=0 || m<=0 )
+            {
+                info = -1;
+                return;
+            }
+            
+            //
+            // solve
+            //
+            spdmatrixcholeskysolveinternal(cha, n, isupper, emptya, false, b, m, ref info, rep, ref x, _params);
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*X=B with N*N symmetric positive definite matrix A given
+        by its Cholesky decomposition, and N*M vectors X and B. It  is  "fast-but-
+        lightweight" version of  the  solver  which  just  solves  linear  system,
+        without any additional functions.
+
+        Algorithm features:
+        * O(M*N^2) complexity
+        * matrix is represented by its upper or lower triangle
+        * no additional functionality
+
+        INPUT PARAMETERS
+            CHA     -   array[N,N], Cholesky decomposition,
+                        SPDMatrixCholesky result
+            N       -   size of CHA
+            IsUpper -   what half of CHA is provided
+            B       -   array[N,M], right part
+            M       -   right part size
+
+        OUTPUT PARAMETERS
+            Info    -   return code:
+                        * -3    A is is exactly singular or badly conditioned
+                                X is filled by zeros in such cases.
+                        * -1    N<=0 was passed
+                        *  1    task was solved
+            B       -   array[N]:
+                        * for info>0 overwritten by solution
+                        * for info=-3 filled by zeros
+
+          -- ALGLIB --
+             Copyright 18.03.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spdmatrixcholeskysolvemfast(double[,] cha,
+            int n,
+            bool isupper,
+            double[,] b,
+            int m,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+
+            info = 0;
+
+            info = 1;
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            for(k=0; k<=n-1; k++)
+            {
+                if( (double)(cha[k,k])==(double)(0.0) )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=0; j<=m-1; j++)
+                        {
+                            b[i,j] = 0.0;
+                        }
+                    }
+                    info = -3;
+                    return;
+                }
+            }
+            if( isupper )
+            {
+                ablas.rmatrixlefttrsm(n, m, cha, 0, 0, true, false, 1, b, 0, 0, _params);
+                ablas.rmatrixlefttrsm(n, m, cha, 0, 0, true, false, 0, b, 0, 0, _params);
+            }
+            else
+            {
+                ablas.rmatrixlefttrsm(n, m, cha, 0, 0, false, false, 0, b, 0, 0, _params);
+                ablas.rmatrixlefttrsm(n, m, cha, 0, 0, false, false, 1, b, 0, 0, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*x=b with N*N symmetric positive definite matrix A given
+        by its Cholesky decomposition, and N*1 real vectors x and b. This is "slow-
+        but-feature-rich"  version  of  the  solver  which,  in  addition  to  the
+        solution, performs condition number estimation.
+
+        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.
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear system,
+                   ! which results in 10-15x  performance  penalty  when  compared
+                   ! with "fast" version which just calls triangular solver.
+                   !
+                   ! This performance penalty is insignificant  when compared with
+                   ! cost of large LU decomposition.  However,  if you  call  this
+                   ! function many times for the same  left  side,  this  overhead
+                   ! BECOMES significant. It  also  becomes significant for small-
+                   ! scale problems (N<50).
+                   !
+                   ! In such cases we strongly recommend you to use faster solver,
+                   ! SPDMatrixCholeskySolveFast() function.
+
+        INPUT PARAMETERS
+            CHA     -   array[N,N], Cholesky decomposition,
+                        SPDMatrixCholesky result
+            N       -   size of A
+            IsUpper -   what half of CHA is provided
+            B       -   array[N], right part
+
+        OUTPUT PARAMETERS
+            Info    -   return code:
+                        * -3    A is is exactly singular or ill conditioned
+                                X is filled by zeros in such cases.
+                        * -1    N<=0 was passed
+                        *  1    task is solved
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N]:
+                        * for info>0  - solution
+                        * for info=-3 - filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            x = new double[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*x=b with N*N symmetric positive definite matrix A given
+        by its Cholesky decomposition, and N*1 real vectors x and b. This is "fast-
+        but-lightweight" version of the solver.
+
+        Algorithm features:
+        * O(N^2) complexity
+        * matrix is represented by its upper or lower triangle
+        * no additional features
+
+        INPUT PARAMETERS
+            CHA     -   array[N,N], Cholesky decomposition,
+                        SPDMatrixCholesky result
+            N       -   size of A
+            IsUpper -   what half of CHA is provided
+            B       -   array[N], right part
+
+        OUTPUT PARAMETERS
+            Info    -   return code:
+                        * -3    A is is exactly singular or ill conditioned
+                                X is filled by zeros in such cases.
+                        * -1    N<=0 was passed
+                        *  1    task is solved
+            B       -   array[N]:
+                        * for info>0  - overwritten by solution
+                        * for info=-3 - filled by zeros
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        public static void spdmatrixcholeskysolvefast(double[,] cha,
+            int n,
+            bool isupper,
+            double[] b,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+
+            info = 0;
+
+            info = 1;
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            for(k=0; k<=n-1; k++)
+            {
+                if( (double)(cha[k,k])==(double)(0.0) )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        b[i] = 0.0;
+                    }
+                    info = -3;
+                    return;
+                }
+            }
+            spdbasiccholeskysolve(cha, n, isupper, b, _params);
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*X=B, with N*N Hermitian positive definite matrix A  and
+        N*M  complex  matrices  X  and  B.  "Slow-but-feature-rich" version of the
+        solver.
+
+        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.
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear system,
+                   ! which  results  in  significant  performance   penalty   when
+                   ! compared with "fast"  version  which  just  calls  triangular
+                   ! solver.
+                   !
+                   ! This performance penalty is especially apparent when you  use
+                   ! ALGLIB parallel capabilities (condition number estimation  is
+                   ! inherently  sequential).  It   also   becomes significant for
+                   ! small-scale problems (N<100).
+                   !
+                   ! In such cases we strongly recommend you to use faster solver,
+                   ! HPDMatrixSolveMFast() function.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            complex[,] da = new complex[0,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];
+            
+            //
+            // factorize matrix, solve
+            //
+            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, _params) )
+            {
+                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, n, isupper, a, true, b, m, ref info, rep, ref x, _params);
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*X=B, with N*N Hermitian positive definite matrix A  and
+        N*M complex matrices X and B. "Fast-but-lightweight" version of the solver.
+
+        Algorithm features:
+        * O(N^3+M*N^2) complexity
+        * matrix is represented by its upper or lower triangle
+        * no additional time consuming features like condition number estimation
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    -   return code:
+                        * -3    A is is exactly  singular or is not positive definite.
+                                B is filled by zeros in such cases.
+                        * -1    N<=0 was passed
+                        *  1    task is solved
+            B       -   array[0..N-1]:
+                        * overwritten by solution
+                        * zeros, if problem was not solved
+
+          -- ALGLIB --
+             Copyright 17.03.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hpdmatrixsolvemfast(complex[,] a,
+            int n,
+            bool isupper,
+            complex[,] b,
+            int m,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+
+            a = (complex[,])a.Clone();
+            info = 0;
+
+            info = 1;
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            if( !trfac.hpdmatrixcholesky(ref a, n, isupper, _params) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        b[i,j] = 0.0;
+                    }
+                }
+                info = -3;
+                return;
+            }
+            if( isupper )
+            {
+                ablas.cmatrixlefttrsm(n, m, a, 0, 0, true, false, 2, b, 0, 0, _params);
+                ablas.cmatrixlefttrsm(n, m, a, 0, 0, true, false, 0, b, 0, 0, _params);
+            }
+            else
+            {
+                ablas.cmatrixlefttrsm(n, m, a, 0, 0, false, false, 0, b, 0, 0, _params);
+                ablas.cmatrixlefttrsm(n, m, a, 0, 0, false, false, 2, b, 0, 0, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*x=b, with N*N Hermitian positive definite matrix A, and
+        N*1 complex vectors  x  and  b.  "Slow-but-feature-rich"  version  of  the
+        solver.
+
+        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.
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear system,
+                   ! which  results  in  significant   performance   penalty  when
+                   ! compared with "fast" version  which  just  performs  Cholesky
+                   ! decomposition and calls triangular solver.
+                   !
+                   ! This  performance  penalty  is  especially  visible  in   the
+                   ! multithreaded mode, because both condition number  estimation
+                   ! and   iterative    refinement   are   inherently   sequential
+                   ! calculations.
+                   !
+                   ! Thus, if you need high performance and if you are pretty sure
+                   ! that your system is well conditioned, we  strongly  recommend
+                   ! you to use faster solver, HPDMatrixSolveFast() function.
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            x = new complex[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*x=b, with N*N Hermitian positive definite matrix A, and
+        N*1 complex vectors  x  and  b.  "Fast-but-lightweight"  version  of   the
+        solver without additional functions.
+
+        Algorithm features:
+        * O(N^3) complexity
+        * matrix is represented by its upper or lower triangle
+        * no additional time consuming functions
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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    -   return code:
+                        * -3    A is is exactly singular or not positive definite
+                                X is filled by zeros in such cases.
+                        * -1    N<=0 was passed
+                        *  1    task was solved 
+            B       -   array[0..N-1]:
+                        * overwritten by solution
+                        * zeros, if A is exactly singular (diagonal of its LU
+                          decomposition has exact zeros).
+
+          -- ALGLIB --
+             Copyright 17.03.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hpdmatrixsolvefast(complex[,] a,
+            int n,
+            bool isupper,
+            complex[] b,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            a = (complex[,])a.Clone();
+            info = 0;
+
+            info = 1;
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            if( !trfac.hpdmatrixcholesky(ref a, n, isupper, _params) )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    b[i] = 0.0;
+                }
+                info = -3;
+                return;
+            }
+            hpdbasiccholeskysolve(a, n, isupper, b, _params);
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*X=B with N*N Hermitian positive definite matrix A given
+        by its Cholesky decomposition and N*M complex matrices X  and  B.  This is
+        "slow-but-feature-rich" version of the solver which, in  addition  to  the
+        solution, estimates condition number of the system.
+
+        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.
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear system,
+                   ! which  results  in  significant  performance   penalty   when
+                   ! compared with "fast"  version  which  just  calls  triangular
+                   ! solver. Amount of  overhead  introduced  depends  on  M  (the
+                   ! larger - the more efficient).
+                   !
+                   ! This performance penalty is insignificant  when compared with
+                   ! cost of large Cholesky decomposition.  However,  if  you call
+                   ! this  function  many  times  for  the same  left  side,  this
+                   ! overhead BECOMES significant. It  also   becomes  significant
+                   ! for small-scale problems (N<50).
+                   !
+                   ! In such cases we strongly recommend you to use faster solver,
+                   ! HPDMatrixCholeskySolveMFast() function.
+
+
+        INPUT PARAMETERS
+            CHA     -   array[N,N], Cholesky decomposition,
+                        HPDMatrixCholesky result
+            N       -   size of CHA
+            IsUpper -   what half of CHA is provided
+            B       -   array[N,M], right part
+            M       -   right part size
+
+        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 was solved
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N]:
+                        * for info>0 contains solution
+                        * for info=-3 filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            complex[,] emptya = new complex[0,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
+            //
+            hpdmatrixcholeskysolveinternal(cha, n, isupper, emptya, false, b, m, ref info, rep, ref x, _params);
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*X=B with N*N Hermitian positive definite matrix A given
+        by its Cholesky decomposition and N*M complex matrices X  and  B.  This is
+        "fast-but-lightweight" version of the solver.
+
+        Algorithm features:
+        * O(M*N^2) complexity
+        * matrix is represented by its upper or lower triangle
+        * no additional time-consuming features
+
+        INPUT PARAMETERS
+            CHA     -   array[N,N], Cholesky decomposition,
+                        HPDMatrixCholesky result
+            N       -   size of CHA
+            IsUpper -   what half of CHA is provided
+            B       -   array[N,M], right part
+            M       -   right part size
+
+        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 was solved
+            B       -   array[N]:
+                        * for info>0 overwritten by solution
+                        * for info=-3 filled by zeros
+
+          -- ALGLIB --
+             Copyright 18.03.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hpdmatrixcholeskysolvemfast(complex[,] cha,
+            int n,
+            bool isupper,
+            complex[,] b,
+            int m,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            int k = 0;
+
+            info = 0;
+
+            info = 1;
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            for(k=0; k<=n-1; k++)
+            {
+                if( (double)(cha[k,k].x)==(double)(0.0) && (double)(cha[k,k].y)==(double)(0.0) )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        for(j=0; j<=m-1; j++)
+                        {
+                            b[i,j] = 0.0;
+                        }
+                    }
+                    info = -3;
+                    return;
+                }
+            }
+            if( isupper )
+            {
+                ablas.cmatrixlefttrsm(n, m, cha, 0, 0, true, false, 2, b, 0, 0, _params);
+                ablas.cmatrixlefttrsm(n, m, cha, 0, 0, true, false, 0, b, 0, 0, _params);
+            }
+            else
+            {
+                ablas.cmatrixlefttrsm(n, m, cha, 0, 0, false, false, 0, b, 0, 0, _params);
+                ablas.cmatrixlefttrsm(n, m, cha, 0, 0, false, false, 2, b, 0, 0, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*x=b with N*N Hermitian positive definite matrix A given
+        by its Cholesky decomposition, and N*1 complex vectors x and  b.  This  is
+        "slow-but-feature-rich" version of the solver  which  estimates  condition
+        number of the system.
+
+        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.
+
+        IMPORTANT: ! this function is NOT the most efficient linear solver provided
+                   ! by ALGLIB. It estimates condition  number  of  linear system,
+                   ! which results in 10-15x  performance  penalty  when  compared
+                   ! with "fast" version which just calls triangular solver.
+                   !
+                   ! This performance penalty is insignificant  when compared with
+                   ! cost of large LU decomposition.  However,  if you  call  this
+                   ! function many times for the same  left  side,  this  overhead
+                   ! BECOMES significant. It  also  becomes significant for small-
+                   ! scale problems (N<50).
+                   !
+                   ! In such cases we strongly recommend you to use faster solver,
+                   ! HPDMatrixCholeskySolveFast() function.
+
+        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    -   return code:
+                        * -3    A is is exactly singular or ill conditioned
+                                X is filled by zeros in such cases.
+                        * -1    N<=0 was passed
+                        *  1    task is solved
+            Rep     -   additional report, following fields are set:
+                        * rep.r1    condition number in 1-norm
+                        * rep.rinf  condition number in inf-norm
+            X       -   array[N]:
+                        * for info>0  - solution
+                        * for info=-3 - filled by zeros
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            x = new complex[n];
+            for(i_=0; i_<=n-1;i_++)
+            {
+                x[i_] = xm[i_,0];
+            }
+        }
+
+
+        /*************************************************************************
+        Dense solver for A*x=b with N*N Hermitian positive definite matrix A given
+        by its Cholesky decomposition, and N*1 complex vectors x and  b.  This  is
+        "fast-but-lightweight" version of the solver.
+
+        Algorithm features:
+        * O(N^2) complexity
+        * matrix is represented by its upper or lower triangle
+        * no additional time-consuming features
+
+        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    -   return code:
+                        * -3    A is is exactly singular or ill conditioned
+                                B is filled by zeros in such cases.
+                        * -1    N<=0 was passed
+                        *  1    task is solved
+            B       -   array[N]:
+                        * for info>0  - overwritten by solution
+                        * for info=-3 - filled by zeros
+
+          -- ALGLIB --
+             Copyright 18.03.2015 by Bochkanov Sergey
+        *************************************************************************/
+        public static void hpdmatrixcholeskysolvefast(complex[,] cha,
+            int n,
+            bool isupper,
+            complex[] b,
+            ref int info,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int k = 0;
+
+            info = 0;
+
+            info = 1;
+            if( n<=0 )
+            {
+                info = -1;
+                return;
+            }
+            for(k=0; k<=n-1; k++)
+            {
+                if( (double)(cha[k,k].x)==(double)(0.0) && (double)(cha[k,k].y)==(double)(0.0) )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        b[i] = 0.0;
+                    }
+                    info = -3;
+                    return;
+                }
+            }
+            hpdbasiccholeskysolve(cha, n, isupper, b, _params);
+        }
+
+
+        /*************************************************************************
+        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 (and correct handling!) of degenerate cases
+        * iterative refinement
+        * O(N^3) complexity
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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 (even for singular A)
+                        * zeros, if SVD subroutine failed
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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, _params);
+            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, _params);
+                        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,
+            int n,
+            double[,] a,
+            bool havea,
+            double[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref double[,] x,
+            alglib.xparams _params)
+        {
+            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;
+            bool smallerr = new bool();
+            bool terminatenexttime = new bool();
+            int i_ = 0;
+
+            info = 0;
+            x = new double[0,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, _params);
+            rep.rinf = rcond.rmatrixlurcondinf(lua, n, _params);
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold(_params)) || (double)(rep.rinf)<(double)(rcond.rcondthreshold(_params)) )
+            {
+                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;
+            
+            //
+            // First stage of solution: rough solution with TRSM()
+            //
+            mxb = 0.0;
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    v = b[i,j];
+                    mxb = Math.Max(mxb, Math.Abs(v));
+                    x[i,j] = v;
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( p[i]!=i )
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        v = x[i,j];
+                        x[i,j] = x[p[i],j];
+                        x[p[i],j] = v;
+                    }
+                }
+            }
+            ablas.rmatrixlefttrsm(n, m, lua, 0, 0, false, true, 0, x, 0, 0, _params);
+            ablas.rmatrixlefttrsm(n, m, lua, 0, 0, true, false, 0, x, 0, 0, _params);
+            
+            //
+            // Second stage: iterative refinement
+            //
+            if( havea )
+            {
+                for(k=0; k<=m-1; k++)
+                {
+                    nrfs = densesolverrfsmax(n, rep.r1, rep.rinf, _params);
+                    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_] = x[i_,k];
+                        }
+                        for(i=0; i<=n-1; i++)
+                        {
+                            for(i_=0; i_<=n-1;i_++)
+                            {
+                                xa[i_] = a[i,i_];
+                            }
+                            xa[n] = -1;
+                            xb[n] = b[i,k];
+                            xblas.xdot(xa, xb, n+1, ref tx, ref v, ref verr, _params);
+                            y[i] = -v;
+                            smallerr = smallerr && (double)(Math.Abs(v))<(double)(4*verr);
+                        }
+                        if( smallerr )
+                        {
+                            terminatenexttime = true;
+                        }
+                        
+                        //
+                        // solve and update
+                        //
+                        rbasiclusolve(lua, p, n, y, _params);
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            x[i_,k] = x[i_,k] + y[i_];
+                        }
+                    }
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal Cholesky solver
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void spdmatrixcholeskysolveinternal(double[,] cha,
+            int n,
+            bool isupper,
+            double[,] a,
+            bool havea,
+            double[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref double[,] x,
+            alglib.xparams _params)
+        {
+            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;
+            }
+            x = new double[n, m];
+            
+            //
+            // estimate condition number, test for near singularity
+            //
+            rep.r1 = rcond.spdmatrixcholeskyrcond(cha, n, isupper, _params);
+            rep.rinf = rep.r1;
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold(_params)) )
+            {
+                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 with TRSM()
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    x[i,j] = b[i,j];
+                }
+            }
+            if( isupper )
+            {
+                ablas.rmatrixlefttrsm(n, m, cha, 0, 0, true, false, 1, x, 0, 0, _params);
+                ablas.rmatrixlefttrsm(n, m, cha, 0, 0, true, false, 0, x, 0, 0, _params);
+            }
+            else
+            {
+                ablas.rmatrixlefttrsm(n, m, cha, 0, 0, false, false, 0, x, 0, 0, _params);
+                ablas.rmatrixlefttrsm(n, m, cha, 0, 0, false, false, 1, x, 0, 0, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        Internal LU solver
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void cmatrixlusolveinternal(complex[,] lua,
+            int[] p,
+            int n,
+            complex[,] a,
+            bool havea,
+            complex[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref complex[,] x,
+            alglib.xparams _params)
+        {
+            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;
+            bool smallerr = new bool();
+            bool terminatenexttime = new bool();
+            int i_ = 0;
+
+            info = 0;
+            x = new complex[0,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, _params);
+            rep.rinf = rcond.cmatrixlurcondinf(lua, n, _params);
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold(_params)) || (double)(rep.rinf)<(double)(rcond.rcondthreshold(_params)) )
+            {
+                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;
+            
+            //
+            // First phase: solve with TRSM()
+            //
+            for(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    x[i,j] = b[i,j];
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                if( p[i]!=i )
+                {
+                    for(j=0; j<=m-1; j++)
+                    {
+                        v = x[i,j];
+                        x[i,j] = x[p[i],j];
+                        x[p[i],j] = v;
+                    }
+                }
+            }
+            ablas.cmatrixlefttrsm(n, m, lua, 0, 0, false, true, 0, x, 0, 0, _params);
+            ablas.cmatrixlefttrsm(n, m, lua, 0, 0, true, false, 0, x, 0, 0, _params);
+            
+            //
+            // solve
+            //
+            for(k=0; k<=m-1; k++)
+            {
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    bc[i_] = b[i_,k];
+                }
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    xc[i_] = x[i_,k];
+                }
+                
+                //
+                // 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, _params);
+                    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_] = a[i,i_];
+                            }
+                            xa[n] = -1;
+                            xb[n] = bc[i];
+                            xblas.xcdot(xa, xb, n+1, ref tmpbuf, ref v, ref verr, _params);
+                            y[i] = -v;
+                            smallerr = smallerr && (double)(math.abscomplex(v))<(double)(4*verr);
+                        }
+                        if( smallerr )
+                        {
+                            terminatenexttime = true;
+                        }
+                        
+                        //
+                        // solve and update
+                        //
+                        cbasiclusolve(lua, p, n, y, _params);
+                        for(i_=0; i_<=n-1;i_++)
+                        {
+                            xc[i_] = xc[i_] + y[i_];
+                        }
+                    }
+                }
+                
+                //
+                // Store xc.
+                // Post-scale result.
+                //
+                for(i_=0; i_<=n-1;i_++)
+                {
+                    x[i_,k] = xc[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Internal Cholesky solver
+
+          -- ALGLIB --
+             Copyright 27.01.2010 by Bochkanov Sergey
+        *************************************************************************/
+        private static void hpdmatrixcholeskysolveinternal(complex[,] cha,
+            int n,
+            bool isupper,
+            complex[,] a,
+            bool havea,
+            complex[,] b,
+            int m,
+            ref int info,
+            densesolverreport rep,
+            ref complex[,] x,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 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];
+
+            info = 0;
+            x = new complex[0,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, _params);
+            rep.rinf = rep.r1;
+            if( (double)(rep.r1)<(double)(rcond.rcondthreshold(_params)) )
+            {
+                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(i=0; i<=n-1; i++)
+            {
+                for(j=0; j<=m-1; j++)
+                {
+                    x[i,j] = b[i,j];
+                }
+            }
+            if( isupper )
+            {
+                ablas.cmatrixlefttrsm(n, m, cha, 0, 0, true, false, 2, x, 0, 0, _params);
+                ablas.cmatrixlefttrsm(n, m, cha, 0, 0, true, false, 0, x, 0, 0, _params);
+            }
+            else
+            {
+                ablas.cmatrixlefttrsm(n, m, cha, 0, 0, false, false, 0, x, 0, 0, _params);
+                ablas.cmatrixlefttrsm(n, m, cha, 0, 0, false, false, 2, x, 0, 0, _params);
+            }
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = densesolverrfsmax(n, 0, 0, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Basic LU solver for PLU*x = y.
+
+        This subroutine assumes that:
+        * 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,
+            int n,
+            double[] xb,
+            alglib.xparams _params)
+        {
+            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]/lua[n-1,n-1];
+            for(i=n-2; i>=0; i--)
+            {
+                v = 0.0;
+                for(i_=i+1; i_<=n-1;i_++)
+                {
+                    v += lua[i,i_]*xb[i_];
+                }
+                xb[i] = (xb[i]-v)/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,
+            int n,
+            bool isupper,
+            double[] xb,
+            alglib.xparams _params)
+        {
+            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]/cha[i,i];
+                    if( i<n-1 )
+                    {
+                        v = xb[i];
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            xb[i_] = xb[i_] - v*cha[i,i_];
+                        }
+                    }
+                }
+                
+                //
+                // Solve U*x=y then.
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    if( i<n-1 )
+                    {
+                        v = 0.0;
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            v += cha[i,i_]*xb[i_];
+                        }
+                        xb[i] = xb[i]-v;
+                    }
+                    xb[i] = xb[i]/cha[i,i];
+                }
+            }
+            else
+            {
+                
+                //
+                // Solve L*y=b first
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    if( i>0 )
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            v += cha[i,i_]*xb[i_];
+                        }
+                        xb[i] = xb[i]-v;
+                    }
+                    xb[i] = xb[i]/cha[i,i];
+                }
+                
+                //
+                // Solve L'*x=y then.
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    xb[i] = xb[i]/cha[i,i];
+                    if( i>0 )
+                    {
+                        v = xb[i];
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            xb[i_] = xb[i_] - v*cha[i,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,
+            int n,
+            complex[] xb,
+            alglib.xparams _params)
+        {
+            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]/lua[n-1,n-1];
+            for(i=n-2; i>=0; i--)
+            {
+                v = 0.0;
+                for(i_=i+1; i_<=n-1;i_++)
+                {
+                    v += lua[i,i_]*xb[i_];
+                }
+                xb[i] = (xb[i]-v)/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,
+            int n,
+            bool isupper,
+            complex[] xb,
+            alglib.xparams _params)
+        {
+            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]/math.conj(cha[i,i]);
+                    if( i<n-1 )
+                    {
+                        v = xb[i];
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            xb[i_] = xb[i_] - v*math.conj(cha[i,i_]);
+                        }
+                    }
+                }
+                
+                //
+                // Solve U*x=y then.
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    if( i<n-1 )
+                    {
+                        v = 0.0;
+                        for(i_=i+1; i_<=n-1;i_++)
+                        {
+                            v += cha[i,i_]*xb[i_];
+                        }
+                        xb[i] = xb[i]-v;
+                    }
+                    xb[i] = xb[i]/cha[i,i];
+                }
+            }
+            else
+            {
+                
+                //
+                // Solve L*y=b first
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    if( i>0 )
+                    {
+                        v = 0.0;
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            v += cha[i,i_]*xb[i_];
+                        }
+                        xb[i] = xb[i]-v;
+                    }
+                    xb[i] = xb[i]/cha[i,i];
+                }
+                
+                //
+                // Solve L'*x=y then.
+                //
+                for(i=n-1; i>=0; i--)
+                {
+                    xb[i] = xb[i]/math.conj(cha[i,i]);
+                    if( i>0 )
+                    {
+                        v = xb[i];
+                        for(i_=0; i_<=i-1;i_++)
+                        {
+                            xb[i_] = xb[i_] - v*math.conj(cha[i,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 : apobject
+        {
+            public normestimator.normestimatorstate nes;
+            public double[] rx;
+            public double[] b;
+            public int n;
+            public int m;
+            public int prectype;
+            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 bool userterminationneeded;
+            public double[] tmpd;
+            public double[] tmpx;
+            public rcommstate rstate;
+            public linlsqrstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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];
+                tmpd = new double[0];
+                tmpx = new double[0];
+                rstate = new rcommstate();
+            }
+            public override alglib.apobject make_copy()
+            {
+                linlsqrstate _result = new linlsqrstate();
+                _result.nes = (normestimator.normestimatorstate)nes.make_copy();
+                _result.rx = (double[])rx.Clone();
+                _result.b = (double[])b.Clone();
+                _result.n = n;
+                _result.m = m;
+                _result.prectype = prectype;
+                _result.ui = (double[])ui.Clone();
+                _result.uip1 = (double[])uip1.Clone();
+                _result.vi = (double[])vi.Clone();
+                _result.vip1 = (double[])vip1.Clone();
+                _result.omegai = (double[])omegai.Clone();
+                _result.omegaip1 = (double[])omegaip1.Clone();
+                _result.alphai = alphai;
+                _result.alphaip1 = alphaip1;
+                _result.betai = betai;
+                _result.betaip1 = betaip1;
+                _result.phibari = phibari;
+                _result.phibarip1 = phibarip1;
+                _result.phii = phii;
+                _result.rhobari = rhobari;
+                _result.rhobarip1 = rhobarip1;
+                _result.rhoi = rhoi;
+                _result.ci = ci;
+                _result.si = si;
+                _result.theta = theta;
+                _result.lambdai = lambdai;
+                _result.d = (double[])d.Clone();
+                _result.anorm = anorm;
+                _result.bnorm2 = bnorm2;
+                _result.dnorm = dnorm;
+                _result.r2 = r2;
+                _result.x = (double[])x.Clone();
+                _result.mv = (double[])mv.Clone();
+                _result.mtv = (double[])mtv.Clone();
+                _result.epsa = epsa;
+                _result.epsb = epsb;
+                _result.epsc = epsc;
+                _result.maxits = maxits;
+                _result.xrep = xrep;
+                _result.xupdated = xupdated;
+                _result.needmv = needmv;
+                _result.needmtv = needmtv;
+                _result.needmv2 = needmv2;
+                _result.needvmv = needvmv;
+                _result.needprec = needprec;
+                _result.repiterationscount = repiterationscount;
+                _result.repnmv = repnmv;
+                _result.repterminationtype = repterminationtype;
+                _result.running = running;
+                _result.userterminationneeded = userterminationneeded;
+                _result.tmpd = (double[])tmpd.Clone();
+                _result.tmpx = (double[])tmpx.Clone();
+                _result.rstate = (rcommstate)rstate.make_copy();
+                return _result;
+            }
+        };
+
+
+        public class linlsqrreport : apobject
+        {
+            public int iterationscount;
+            public int nmv;
+            public int terminationtype;
+            public linlsqrreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                linlsqrreport _result = new linlsqrreport();
+                _result.iterationscount = iterationscount;
+                _result.nmv = nmv;
+                _result.terminationtype = terminationtype;
+                return _result;
+            }
+        };
+
+
+
+
+        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
+            
+        NOTE: see also linlsqrcreatebuf()  for  version  which  reuses  previously
+              allocated place as much as possible.
+
+          -- ALGLIB --
+             Copyright 30.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrcreate(int m,
+            int n,
+            linlsqrstate state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(m>0, "LinLSQRCreate: M<=0");
+            alglib.ap.assert(n>0, "LinLSQRCreate: N<=0");
+            linlsqrcreatebuf(m, n, state, _params);
+        }
+
+
+        /*************************************************************************
+        This function initializes linear LSQR Solver.  It  provides  exactly  same
+        functionality as linlsqrcreate(), but reuses  previously  allocated  space
+        as much as possible.
+          
+        INPUT PARAMETERS:
+            M       -   number of rows in A
+            N       -   number of variables, N>0
+
+        OUTPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 14.11.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrcreatebuf(int m,
+            int n,
+            linlsqrstate state,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(m>0, "LinLSQRCreateBuf: M<=0");
+            alglib.ap.assert(n>0, "LinLSQRCreateBuf: N<=0");
+            state.m = m;
+            state.n = n;
+            state.prectype = 0;
+            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;
+            state.repiterationscount = 0;
+            
+            //
+            // * 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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params), "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  changes  preconditioning  settings of LinLSQQSolveSparse()
+        function. By default, SolveSparse() uses diagonal preconditioner,  but  if
+        you want to use solver without preconditioning, you can call this function
+        which forces solver to use unit matrix for preconditioning.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 19.11.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrsetprecunit(linlsqrstate state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(!state.running, "LinLSQRSetPrecUnit: you can not change preconditioner, because function LinLSQRIteration is running!");
+            state.prectype = -1;
+        }
+
+
+        /*************************************************************************
+        This  function  changes  preconditioning  settings  of  LinCGSolveSparse()
+        function.  LinCGSolveSparse() will use diagonal of the  system  matrix  as
+        preconditioner. This preconditioning mode is active by default.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 19.11.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrsetprecdiag(linlsqrstate state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(!state.running, "LinLSQRSetPrecDiag: you can not change preconditioner, because function LinCGIteration is running!");
+            state.prectype = 0;
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 = 359;
+                i = -58;
+                bnorm = -919;
+            }
+            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");
+            summn = state.m+state.n;
+            bnorm = Math.Sqrt(state.bnorm2);
+            state.userterminationneeded = false;
+            state.running = true;
+            state.repnmv = 0;
+            state.repiterationscount = 0;
+            state.r2 = state.bnorm2;
+            clearrfields(state, _params);
+            
+            //
+            //estimate for ANorm
+            //
+            normestimator.normestimatorrestart(state.nes, _params);
+        lbl_7:
+            if( !normestimator.normestimatoriteration(state.nes, _params) )
+            {
+                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, _params);
+            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, _params);
+            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, _params);
+            
+            //
+            //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, _params);
+            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, _params);
+            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, _params);
+            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, _params);
+            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, _params);
+            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, _params);
+            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;
+            }
+            if( state.userterminationneeded )
+            {
+                
+                //
+                // User requested termination
+                //
+                state.running = false;
+                state.repterminationtype = 8;
+                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()
+            
+        NOTE: this function uses lightweight preconditioning -  multiplication  by
+              inverse of diag(A). If you want, you can turn preconditioning off by
+              calling LinLSQRSetPrecUnit(). However, preconditioning cost is   low
+              and preconditioner is very important for solution  of  badly  scaled
+              problems.
+
+          -- ALGLIB --
+             Copyright 30.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrsolvesparse(linlsqrstate state,
+            sparse.sparsematrix a,
+            double[] b,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            int j = 0;
+            int t0 = 0;
+            int t1 = 0;
+            double v = 0;
+
+            n = state.n;
+            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, _params), "LinLSQRSolveSparse: B contains infinite or NaN values");
+            
+            //
+            // Allocate temporaries
+            //
+            apserv.rvectorsetlengthatleast(ref state.tmpd, n, _params);
+            apserv.rvectorsetlengthatleast(ref state.tmpx, n, _params);
+            
+            //
+            // Compute diagonal scaling matrix D
+            //
+            if( state.prectype==0 )
+            {
+                
+                //
+                // Default preconditioner - inverse of column norms
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    state.tmpd[i] = 0;
+                }
+                t0 = 0;
+                t1 = 0;
+                while( sparse.sparseenumerate(a, ref t0, ref t1, ref i, ref j, ref v, _params) )
+                {
+                    state.tmpd[j] = state.tmpd[j]+math.sqr(v);
+                }
+                for(i=0; i<=n-1; i++)
+                {
+                    if( (double)(state.tmpd[i])>(double)(0) )
+                    {
+                        state.tmpd[i] = 1/Math.Sqrt(state.tmpd[i]);
+                    }
+                    else
+                    {
+                        state.tmpd[i] = 1;
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // No diagonal scaling
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    state.tmpd[i] = 1;
+                }
+            }
+            
+            //
+            // Solve.
+            //
+            // Instead of solving A*x=b we solve preconditioned system (A*D)*(inv(D)*x)=b.
+            // Transformed A is not calculated explicitly, we just modify multiplication
+            // by A or A'. After solution we modify State.RX so it will store untransformed
+            // variables
+            //
+            linlsqrsetb(state, b, _params);
+            linlsqrrestart(state, _params);
+            while( linlsqriteration(state, _params) )
+            {
+                if( state.needmv )
+                {
+                    for(i=0; i<=n-1; i++)
+                    {
+                        state.tmpx[i] = state.tmpd[i]*state.x[i];
+                    }
+                    sparse.sparsemv(a, state.tmpx, ref state.mv, _params);
+                }
+                if( state.needmtv )
+                {
+                    sparse.sparsemtv(a, state.x, ref state.mtv, _params);
+                    for(i=0; i<=n-1; i++)
+                    {
+                        state.mtv[i] = state.tmpd[i]*state.mtv[i];
+                    }
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                state.rx[i] = state.tmpd[i]*state.rx[i];
+            }
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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)
+                            *  8    user requested termination via calling
+                                    linlsqrrequesttermination()
+                        * 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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        This function restarts LinLSQRIteration
+
+          -- ALGLIB --
+             Copyright 30.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrrestart(linlsqrstate state,
+            alglib.xparams _params)
+        {
+            state.rstate.ia = new int[1+1];
+            state.rstate.ra = new double[0+1];
+            state.rstate.stage = -1;
+            clearrfields(state, _params);
+            state.repiterationscount = 0;
+        }
+
+
+        /*************************************************************************
+        This function is used to peek into LSQR solver and get  current  iteration
+        counter. You can safely "peek" into the solver from another thread.
+
+        INPUT PARAMETERS:
+            S           -   solver object
+
+        RESULT:
+            iteration counter, in [0,INF)
+
+          -- ALGLIB --
+             Copyright 21.05.2018 by Bochkanov Sergey
+        *************************************************************************/
+        public static int linlsqrpeekiterationscount(linlsqrstate s,
+            alglib.xparams _params)
+        {
+            int result = 0;
+
+            result = s.repiterationscount;
+            return result;
+        }
+
+
+        /*************************************************************************
+        This subroutine submits request for termination of the running solver.  It
+        can be called from some other thread which wants LSQR solver to  terminate
+        (obviously, the  thread  running  LSQR  solver can not request termination
+        because it is already busy working on LSQR).
+
+        As result, solver  stops  at  point  which  was  "current  accepted"  when
+        termination  request  was  submitted  and returns error code 8 (successful
+        termination).  Such   termination   is  a smooth  process  which  properly
+        deallocates all temporaries.
+
+        INPUT PARAMETERS:
+            State   -   solver structure
+
+        NOTE: calling this function on solver which is NOT running  will  have  no
+              effect.
+              
+        NOTE: multiple calls to this function are possible. First call is counted,
+              subsequent calls are silently ignored.
+
+        NOTE: solver clears termination flag on its start, it means that  if  some
+              other thread will request termination too soon, its request will went
+              unnoticed.
+
+          -- ALGLIB --
+             Copyright 08.10.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void linlsqrrequesttermination(linlsqrstate state,
+            alglib.xparams _params)
+        {
+            state.userterminationneeded = true;
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrfields(linlsqrstate state,
+            alglib.xparams _params)
+        {
+            state.xupdated = false;
+            state.needmv = false;
+            state.needmtv = false;
+            state.needmv2 = false;
+            state.needvmv = false;
+            state.needprec = false;
+        }
+
+
+    }
+    public class polynomialsolver
+    {
+        public class polynomialsolverreport : apobject
+        {
+            public double maxerr;
+            public polynomialsolverreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                polynomialsolverreport _result = new polynomialsolverreport();
+                _result.maxerr = maxerr;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        Polynomial root finding.
+
+        This function returns all roots of the polynomial
+            P(x) = a0 + a1*x + a2*x^2 + ... + an*x^n
+        Both real and complex roots are returned (see below).
+
+        INPUT PARAMETERS:
+            A       -   array[N+1], polynomial coefficients:
+                        * A[0] is constant term
+                        * A[N] is a coefficient of X^N
+            N       -   polynomial degree
+
+        OUTPUT PARAMETERS:
+            X       -   array of complex roots:
+                        * for isolated real root, X[I] is strictly real: IMAGE(X[I])=0
+                        * complex roots are always returned in pairs - roots occupy
+                          positions I and I+1, with:
+                          * X[I+1]=Conj(X[I])
+                          * IMAGE(X[I]) > 0
+                          * IMAGE(X[I+1]) = -IMAGE(X[I]) < 0
+                        * multiple real roots may have non-zero imaginary part due
+                          to roundoff errors. There is no reliable way to distinguish
+                          real root of multiplicity 2 from two  complex  roots  in
+                          the presence of roundoff errors.
+            Rep     -   report, additional information, following fields are set:
+                        * Rep.MaxErr - max( |P(xi)| )  for  i=0..N-1.  This  field
+                          allows to quickly estimate "quality" of the roots  being
+                          returned.
+
+        NOTE:   this function uses companion matrix method to find roots. In  case
+                internal EVD  solver  fails  do  find  eigenvalues,  exception  is
+                generated.
+
+        NOTE:   roots are not "polished" and  no  matrix  balancing  is  performed
+                for them.
+
+          -- ALGLIB --
+             Copyright 24.02.2014 by Bochkanov Sergey
+        *************************************************************************/
+        public static void polynomialsolve(double[] a,
+            int n,
+            ref complex[] x,
+            polynomialsolverreport rep,
+            alglib.xparams _params)
+        {
+            double[,] c = new double[0,0];
+            double[,] vl = new double[0,0];
+            double[,] vr = new double[0,0];
+            double[] wr = new double[0];
+            double[] wi = new double[0];
+            int i = 0;
+            int j = 0;
+            bool status = new bool();
+            int nz = 0;
+            int ne = 0;
+            complex v = 0;
+            complex vv = 0;
+
+            a = (double[])a.Clone();
+            x = new complex[0];
+
+            alglib.ap.assert(n>0, "PolynomialSolve: N<=0");
+            alglib.ap.assert(alglib.ap.len(a)>=n+1, "PolynomialSolve: Length(A)<N+1");
+            alglib.ap.assert(apserv.isfinitevector(a, n+1, _params), "PolynomialSolve: A contains infitite numbers");
+            alglib.ap.assert((double)(a[n])!=(double)(0), "PolynomialSolve: A[N]=0");
+            
+            //
+            // Prepare
+            //
+            x = new complex[n];
+            
+            //
+            // Normalize A:
+            // * analytically determine NZ zero roots
+            // * quick exit for NZ=N
+            // * make residual NE-th degree polynomial monic
+            //   (here NE=N-NZ)
+            //
+            nz = 0;
+            while( nz<n && (double)(a[nz])==(double)(0) )
+            {
+                nz = nz+1;
+            }
+            ne = n-nz;
+            for(i=nz; i<=n; i++)
+            {
+                a[i-nz] = a[i]/a[n];
+            }
+            
+            //
+            // For NZ<N, build companion matrix and find NE non-zero roots
+            //
+            if( ne>0 )
+            {
+                c = new double[ne, ne];
+                for(i=0; i<=ne-1; i++)
+                {
+                    for(j=0; j<=ne-1; j++)
+                    {
+                        c[i,j] = 0;
+                    }
+                }
+                c[0,ne-1] = -a[0];
+                for(i=1; i<=ne-1; i++)
+                {
+                    c[i,i-1] = 1;
+                    c[i,ne-1] = -a[i];
+                }
+                status = evd.rmatrixevd(c, ne, 0, ref wr, ref wi, ref vl, ref vr, _params);
+                alglib.ap.assert(status, "PolynomialSolve: inernal error - EVD solver failed");
+                for(i=0; i<=ne-1; i++)
+                {
+                    x[i].x = wr[i];
+                    x[i].y = wi[i];
+                }
+            }
+            
+            //
+            // Remaining NZ zero roots
+            //
+            for(i=ne; i<=n-1; i++)
+            {
+                x[i] = 0;
+            }
+            
+            //
+            // Rep
+            //
+            rep.maxerr = 0;
+            for(i=0; i<=ne-1; i++)
+            {
+                v = 0;
+                vv = 1;
+                for(j=0; j<=ne; j++)
+                {
+                    v = v+a[j]*vv;
+                    vv = vv*x[i];
+                }
+                rep.maxerr = Math.Max(rep.maxerr, math.abscomplex(v));
+            }
+        }
+
+
+    }
+    public class nleq
+    {
+        public class nleqstate : apobject
+        {
+            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()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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 override alglib.apobject make_copy()
+            {
+                nleqstate _result = new nleqstate();
+                _result.n = n;
+                _result.m = m;
+                _result.epsf = epsf;
+                _result.maxits = maxits;
+                _result.xrep = xrep;
+                _result.stpmax = stpmax;
+                _result.x = (double[])x.Clone();
+                _result.f = f;
+                _result.fi = (double[])fi.Clone();
+                _result.j = (double[,])j.Clone();
+                _result.needf = needf;
+                _result.needfij = needfij;
+                _result.xupdated = xupdated;
+                _result.rstate = (rcommstate)rstate.make_copy();
+                _result.repiterationscount = repiterationscount;
+                _result.repnfunc = repnfunc;
+                _result.repnjac = repnjac;
+                _result.repterminationtype = repterminationtype;
+                _result.xbase = (double[])xbase.Clone();
+                _result.fbase = fbase;
+                _result.fprev = fprev;
+                _result.candstep = (double[])candstep.Clone();
+                _result.rightpart = (double[])rightpart.Clone();
+                _result.cgbuf = (double[])cgbuf.Clone();
+                return _result;
+            }
+        };
+
+
+        public class nleqreport : apobject
+        {
+            public int iterationscount;
+            public int nfunc;
+            public int njac;
+            public int terminationtype;
+            public nleqreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                nleqreport _result = new nleqreport();
+                _result.iterationscount = iterationscount;
+                _result.nfunc = nfunc;
+                _result.njac = njac;
+                _result.terminationtype = terminationtype;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+                        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.xparams _params)
+        {
+            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, _params), "NLEQCreateLM: X contains infinite or NaN values!");
+            
+            //
+            // Initialize
+            //
+            state.n = n;
+            state.m = m;
+            nleqsetcond(state, 0, 0, _params);
+            nleqsetxrep(state, false, _params);
+            nleqsetstpmax(state, 0, _params);
+            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, _params);
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 = 359;
+                m = -58;
+                i = -919;
+                b = true;
+                lambdaup = 81;
+                lambdadown = 255;
+                lambdav = 74;
+                rho = -788;
+                mu = 809;
+                stepnorm = 205;
+            }
+            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, _params);
+            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, _params);
+            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, _params);
+            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, state.rightpart, 0, _params);
+            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, _params);
+            
+            //
+            // 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, _params);
+            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, _params);
+            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, _params);
+                goto lbl_10;
+            }
+            if( !increaselambda(ref lambdav, ref rho, lambdaup, _params) )
+            {
+                
+                //
+                // 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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            x = new double[0];
+
+            nleqresultsbuf(state, ref x, rep, _params);
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            int i_ = 0;
+
+            alglib.ap.assert(alglib.ap.len(x)>=state.n, "NLEQRestartFrom: Length(X)<N!");
+            alglib.ap.assert(apserv.isfinitevector(x, state.n, _params), "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, _params);
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrequestfields(nleqstate state,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            nu = 1;
+            if( (double)(Math.Log(lambdav)+Math.Log(lambdadown))<(double)(Math.Log(math.minrealnumber)) )
+            {
+                lambdav = math.minrealnumber;
+            }
+            else
+            {
+                lambdav = lambdav*lambdadown;
+            }
+        }
+
+
+    }
+    public class directsparsesolvers
+    {
+        /*************************************************************************
+        This structure is a sparse solver report.
+
+        Following fields can be accessed by users:
+        *************************************************************************/
+        public class sparsesolverreport : apobject
+        {
+            public int terminationtype;
+            public sparsesolverreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                sparsesolverreport _result = new sparsesolverreport();
+                _result.terminationtype = terminationtype;
+                return _result;
+            }
+        };
+
+
+
+
+        /*************************************************************************
+        Sparse linear solver for A*x=b with N*N  sparse  real  symmetric  positive
+        definite matrix A, N*1 vectors x and b.
+
+        This solver  converts  input  matrix  to  SKS  format,  performs  Cholesky
+        factorization using  SKS  Cholesky  subroutine  (works  well  for  limited
+        bandwidth matrices) and uses sparse triangular solvers to get solution  of
+        the original system.
+
+        INPUT PARAMETERS
+            A       -   sparse matrix, must be NxN exactly
+            N       -   size of A, N>0
+            IsUpper -   which half of A is provided (another half is ignored)
+            B       -   array[0..N-1], right part
+
+        OUTPUT PARAMETERS
+            Rep     -   solver report, following fields are set:
+                        * rep.terminationtype - solver status; >0 for success,
+                          set to -3 on failure (degenerate or non-SPD system).
+            X       -   array[N], it contains:
+                        * rep.terminationtype>0    =>  solution
+                        * rep.terminationtype=-3   =>  filled by zeros
+
+          -- ALGLIB --
+             Copyright 26.12.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsesolvesks(sparse.sparsematrix a,
+            int n,
+            bool isupper,
+            double[] b,
+            sparsesolverreport rep,
+            ref double[] x,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            sparse.sparsematrix a2 = new sparse.sparsematrix();
+
+            x = new double[0];
+
+            alglib.ap.assert(n>0, "SparseSolveSKS: N<=0");
+            alglib.ap.assert(sparse.sparsegetnrows(a, _params)==n, "SparseSolveSKS: rows(A)!=N");
+            alglib.ap.assert(sparse.sparsegetncols(a, _params)==n, "SparseSolveSKS: cols(A)!=N");
+            alglib.ap.assert(alglib.ap.len(b)>=n, "SparseSolveSKS: length(B)<N");
+            alglib.ap.assert(apserv.isfinitevector(b, n, _params), "SparseSolveSKS: B contains infinities or NANs");
+            initreport(rep, _params);
+            x = new double[n];
+            sparse.sparsecopytosks(a, a2, _params);
+            if( !trfac.sparsecholeskyskyline(a2, n, isupper, _params) )
+            {
+                rep.terminationtype = -3;
+                for(i=0; i<=n-1; i++)
+                {
+                    x[i] = 0;
+                }
+                return;
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                x[i] = b[i];
+            }
+            if( isupper )
+            {
+                sparse.sparsetrsv(a2, isupper, false, 1, x, _params);
+                sparse.sparsetrsv(a2, isupper, false, 0, x, _params);
+            }
+            else
+            {
+                sparse.sparsetrsv(a2, isupper, false, 0, x, _params);
+                sparse.sparsetrsv(a2, isupper, false, 1, x, _params);
+            }
+            rep.terminationtype = 1;
+        }
+
+
+        /*************************************************************************
+        Sparse linear solver for A*x=b with N*N real  symmetric  positive definite
+        matrix A given by its Cholesky decomposition, and N*1 vectors x and b.
+
+        IMPORTANT: this solver requires input matrix to be in  the  SKS  (Skyline)
+                   sparse storage format. An exception will be  generated  if  you
+                   pass matrix in some other format (HASH or CRS).
+
+        INPUT PARAMETERS
+            A       -   sparse NxN matrix stored in SKS format, must be NxN exactly
+            N       -   size of A, N>0
+            IsUpper -   which half of A is provided (another half is ignored)
+            B       -   array[N], right part
+
+        OUTPUT PARAMETERS
+            Rep     -   solver report, following fields are set:
+                        * rep.terminationtype - solver status; >0 for success,
+                          set to -3 on failure (degenerate or non-SPD system).
+            X       -   array[N], it contains:
+                        * rep.terminationtype>0    =>  solution
+                        * rep.terminationtype=-3   =>  filled by zeros
+
+          -- ALGLIB --
+             Copyright 26.12.2017 by Bochkanov Sergey
+        *************************************************************************/
+        public static void sparsecholeskysolvesks(sparse.sparsematrix a,
+            int n,
+            bool isupper,
+            double[] b,
+            sparsesolverreport rep,
+            ref double[] x,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            x = new double[0];
+
+            alglib.ap.assert(n>0, "SparseSolveSKS: N<=0");
+            alglib.ap.assert(sparse.sparsegetnrows(a, _params)==n, "SparseSolveSKS: rows(A)!=N");
+            alglib.ap.assert(sparse.sparsegetncols(a, _params)==n, "SparseSolveSKS: cols(A)!=N");
+            alglib.ap.assert(sparse.sparseissks(a, _params), "SparseSolveSKS: A is not an SKS matrix");
+            alglib.ap.assert(alglib.ap.len(b)>=n, "SparseSolveSKS: length(B)<N");
+            alglib.ap.assert(apserv.isfinitevector(b, n, _params), "SparseSolveSKS: B contains infinities or NANs");
+            initreport(rep, _params);
+            x = new double[n];
+            for(i=0; i<=n-1; i++)
+            {
+                if( (double)(sparse.sparseget(a, i, i, _params))==(double)(0.0) )
+                {
+                    rep.terminationtype = -3;
+                    for(i=0; i<=n-1; i++)
+                    {
+                        x[i] = 0;
+                    }
+                    return;
+                }
+            }
+            for(i=0; i<=n-1; i++)
+            {
+                x[i] = b[i];
+            }
+            if( isupper )
+            {
+                sparse.sparsetrsv(a, isupper, false, 1, x, _params);
+                sparse.sparsetrsv(a, isupper, false, 0, x, _params);
+            }
+            else
+            {
+                sparse.sparsetrsv(a, isupper, false, 0, x, _params);
+                sparse.sparsetrsv(a, isupper, false, 1, x, _params);
+            }
+            rep.terminationtype = 1;
+        }
+
+
+        /*************************************************************************
+        Reset report fields
+
+          -- ALGLIB --
+             Copyright 26.12.2017 by Bochkanov Sergey
+        *************************************************************************/
+        private static void initreport(sparsesolverreport rep,
+            alglib.xparams _params)
+        {
+            rep.terminationtype = 0;
+        }
+
+
+    }
+    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 : apobject
+        {
+            public double[] rx;
+            public double[] b;
+            public int n;
+            public int prectype;
+            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 double[] tmpd;
+            public rcommstate rstate;
+            public lincgstate()
+            {
+                init();
+            }
+            public override void init()
+            {
+                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];
+                tmpd = new double[0];
+                rstate = new rcommstate();
+            }
+            public override alglib.apobject make_copy()
+            {
+                lincgstate _result = new lincgstate();
+                _result.rx = (double[])rx.Clone();
+                _result.b = (double[])b.Clone();
+                _result.n = n;
+                _result.prectype = prectype;
+                _result.cx = (double[])cx.Clone();
+                _result.cr = (double[])cr.Clone();
+                _result.cz = (double[])cz.Clone();
+                _result.p = (double[])p.Clone();
+                _result.r = (double[])r.Clone();
+                _result.z = (double[])z.Clone();
+                _result.alpha = alpha;
+                _result.beta = beta;
+                _result.r2 = r2;
+                _result.meritfunction = meritfunction;
+                _result.x = (double[])x.Clone();
+                _result.mv = (double[])mv.Clone();
+                _result.pv = (double[])pv.Clone();
+                _result.vmv = vmv;
+                _result.startx = (double[])startx.Clone();
+                _result.epsf = epsf;
+                _result.maxits = maxits;
+                _result.itsbeforerestart = itsbeforerestart;
+                _result.itsbeforerupdate = itsbeforerupdate;
+                _result.xrep = xrep;
+                _result.xupdated = xupdated;
+                _result.needmv = needmv;
+                _result.needmtv = needmtv;
+                _result.needmv2 = needmv2;
+                _result.needvmv = needvmv;
+                _result.needprec = needprec;
+                _result.repiterationscount = repiterationscount;
+                _result.repnmv = repnmv;
+                _result.repterminationtype = repterminationtype;
+                _result.running = running;
+                _result.tmpd = (double[])tmpd.Clone();
+                _result.rstate = (rcommstate)rstate.make_copy();
+                return _result;
+            }
+        };
+
+
+        public class lincgreport : apobject
+        {
+            public int iterationscount;
+            public int nmv;
+            public int terminationtype;
+            public double r2;
+            public lincgreport()
+            {
+                init();
+            }
+            public override void init()
+            {
+            }
+            public override alglib.apobject make_copy()
+            {
+                lincgreport _result = new lincgreport();
+                _result.iterationscount = iterationscount;
+                _result.nmv = nmv;
+                _result.terminationtype = terminationtype;
+                _result.r2 = r2;
+                return _result;
+            }
+        };
+
+
+
+
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+
+            alglib.ap.assert(n>0, "LinCGCreate: N<=0");
+            state.n = n;
+            state.prectype = 0;
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params), "LinCGSetB: B contains infinite or NaN values!");
+            for(i_=0; i_<=state.n-1;i_++)
+            {
+                state.b[i_] = b[i_];
+            }
+        }
+
+
+        /*************************************************************************
+        This  function  changes  preconditioning  settings  of  LinCGSolveSparse()
+        function. By default, SolveSparse() uses diagonal preconditioner,  but  if
+        you want to use solver without preconditioning, you can call this function
+        which forces solver to use unit matrix for preconditioning.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 19.11.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgsetprecunit(lincgstate state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(!state.running, "LinCGSetPrecUnit: you can not change preconditioner, because function LinCGIteration is running!");
+            state.prectype = -1;
+        }
+
+
+        /*************************************************************************
+        This  function  changes  preconditioning  settings  of  LinCGSolveSparse()
+        function.  LinCGSolveSparse() will use diagonal of the  system  matrix  as
+        preconditioner. This preconditioning mode is active by default.
+
+        INPUT PARAMETERS:
+            State   -   structure which stores algorithm state
+
+          -- ALGLIB --
+             Copyright 19.11.2012 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgsetprecdiag(lincgstate state,
+            alglib.xparams _params)
+        {
+            alglib.ap.assert(!state.running, "LinCGSetPrecDiag: you can not change preconditioner, because function LinCGIteration is running!");
+            state.prectype = 0;
+        }
+
+
+        /*************************************************************************
+        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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 = 359;
+                uvar = -58;
+                bnorm = -919;
+                v = -909;
+            }
+            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, _params);
+            updateitersdata(state, _params);
+            
+            //
+            // 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, _params);
+            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, _params);
+            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, _params);
+            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, _params);
+            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, _params);
+            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, _params);
+            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, _params);
+            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, _params);
+            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()
+            
+        NOTE: this function uses lightweight preconditioning -  multiplication  by
+              inverse of diag(A). If you want, you can turn preconditioning off by
+              calling LinCGSetPrecUnit(). However, preconditioning cost is low and
+              preconditioner  is  very  important  for  solution  of  badly scaled
+              problems.
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgsolvesparse(lincgstate state,
+            sparse.sparsematrix a,
+            bool isupper,
+            double[] b,
+            alglib.xparams _params)
+        {
+            int n = 0;
+            int i = 0;
+            double v = 0;
+            double vmv = 0;
+            int i_ = 0;
+
+            n = state.n;
+            alglib.ap.assert(alglib.ap.len(b)>=state.n, "LinCGSetB: Length(B)<N");
+            alglib.ap.assert(apserv.isfinitevector(b, state.n, _params), "LinCGSetB: B contains infinite or NaN values!");
+            
+            //
+            // Allocate temporaries
+            //
+            apserv.rvectorsetlengthatleast(ref state.tmpd, n, _params);
+            
+            //
+            // Compute diagonal scaling matrix D
+            //
+            if( state.prectype==0 )
+            {
+                
+                //
+                // Default preconditioner - inverse of matrix diagonal
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    v = sparse.sparsegetdiagonal(a, i, _params);
+                    if( (double)(v)>(double)(0) )
+                    {
+                        state.tmpd[i] = 1/Math.Sqrt(v);
+                    }
+                    else
+                    {
+                        state.tmpd[i] = 1;
+                    }
+                }
+            }
+            else
+            {
+                
+                //
+                // No diagonal scaling
+                //
+                for(i=0; i<=n-1; i++)
+                {
+                    state.tmpd[i] = 1;
+                }
+            }
+            
+            //
+            // Solve
+            //
+            lincgrestart(state, _params);
+            lincgsetb(state, b, _params);
+            while( lincgiteration(state, _params) )
+            {
+                
+                //
+                // Process different requests from optimizer
+                //
+                if( state.needmv )
+                {
+                    sparse.sparsesmv(a, isupper, state.x, ref state.mv, _params);
+                }
+                if( state.needvmv )
+                {
+                    sparse.sparsesmv(a, isupper, state.x, ref state.mv, _params);
+                    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<=n-1; i++)
+                    {
+                        state.pv[i] = state.x[i]*math.sqr(state.tmpd[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,
+            alglib.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            state.xrep = needxrep;
+        }
+
+
+        /*************************************************************************
+        Procedure for restart function LinCGIteration
+
+          -- ALGLIB --
+             Copyright 14.11.2011 by Bochkanov Sergey
+        *************************************************************************/
+        public static void lincgrestart(lincgstate state,
+            alglib.xparams _params)
+        {
+            state.rstate.ia = new int[0+1];
+            state.rstate.ra = new double[2+1];
+            state.rstate.stage = -1;
+            clearrfields(state, _params);
+        }
+
+
+        /*************************************************************************
+        Clears request fileds (to be sure that we don't forgot to clear something)
+        *************************************************************************/
+        private static void clearrfields(lincgstate state,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            state.repiterationscount = 0;
+            state.repnmv = 0;
+            state.repterminationtype = 0;
+        }
+
+
+    }
+}
+
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/specialfunctions.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/specialfunctions.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/specialfunctions.cs	(revision 17158)
@@ -0,0 +1,9406 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 164
+#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)
+    {
+    
+        return gammafunc.gammafunction(x, null);
+    }
+    
+    public static double gammafunction(double x, alglib.xparams _params)
+    {
+    
+        return gammafunc.gammafunction(x, _params);
+    }
+    
+    /*************************************************************************
+    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;
+        return gammafunc.lngamma(x, ref sgngam, null);
+    }
+    
+    public static double lngamma(double x, out double sgngam, alglib.xparams _params)
+    {
+        sgngam = 0;
+        return gammafunc.lngamma(x, ref sgngam, _params);
+    }
+
+}
+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)
+    {
+    
+        return normaldistr.errorfunction(x, null);
+    }
+    
+    public static double errorfunction(double x, alglib.xparams _params)
+    {
+    
+        return normaldistr.errorfunction(x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return normaldistr.errorfunctionc(x, null);
+    }
+    
+    public static double errorfunctionc(double x, alglib.xparams _params)
+    {
+    
+        return normaldistr.errorfunctionc(x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return normaldistr.normaldistribution(x, null);
+    }
+    
+    public static double normaldistribution(double x, alglib.xparams _params)
+    {
+    
+        return normaldistr.normaldistribution(x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return normaldistr.inverf(e, null);
+    }
+    
+    public static double inverf(double e, alglib.xparams _params)
+    {
+    
+        return normaldistr.inverf(e, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return normaldistr.invnormaldistribution(y0, null);
+    }
+    
+    public static double invnormaldistribution(double y0, alglib.xparams _params)
+    {
+    
+        return normaldistr.invnormaldistribution(y0, _params);
+    }
+
+}
+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)
+    {
+    
+        return igammaf.incompletegamma(a, x, null);
+    }
+    
+    public static double incompletegamma(double a, double x, alglib.xparams _params)
+    {
+    
+        return igammaf.incompletegamma(a, x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return igammaf.incompletegammac(a, x, null);
+    }
+    
+    public static double incompletegammac(double a, double x, alglib.xparams _params)
+    {
+    
+        return igammaf.incompletegammac(a, x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return igammaf.invincompletegammac(a, y0, null);
+    }
+    
+    public static double invincompletegammac(double a, double y0, alglib.xparams _params)
+    {
+    
+        return igammaf.invincompletegammac(a, y0, _params);
+    }
+
+}
+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)
+    {
+    
+        return elliptic.ellipticintegralk(m, null);
+    }
+    
+    public static double ellipticintegralk(double m, alglib.xparams _params)
+    {
+    
+        return elliptic.ellipticintegralk(m, _params);
+    }
+    
+    /*************************************************************************
+    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 Math Library, Release 2.8:  June, 2000
+    Copyright 1984, 1987, 2000 by Stephen L. Moshier
+    *************************************************************************/
+    public static double ellipticintegralkhighprecision(double m1)
+    {
+    
+        return elliptic.ellipticintegralkhighprecision(m1, null);
+    }
+    
+    public static double ellipticintegralkhighprecision(double m1, alglib.xparams _params)
+    {
+    
+        return elliptic.ellipticintegralkhighprecision(m1, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return elliptic.incompleteellipticintegralk(phi, m, null);
+    }
+    
+    public static double incompleteellipticintegralk(double phi, double m, alglib.xparams _params)
+    {
+    
+        return elliptic.incompleteellipticintegralk(phi, m, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return elliptic.ellipticintegrale(m, null);
+    }
+    
+    public static double ellipticintegrale(double m, alglib.xparams _params)
+    {
+    
+        return elliptic.ellipticintegrale(m, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return elliptic.incompleteellipticintegrale(phi, m, null);
+    }
+    
+    public static double incompleteellipticintegrale(double phi, double m, alglib.xparams _params)
+    {
+    
+        return elliptic.incompleteellipticintegrale(phi, m, _params);
+    }
+
+}
+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)
+    {
+    
+        return hermite.hermitecalculate(n, x, null);
+    }
+    
+    public static double hermitecalculate(int n, double x, alglib.xparams _params)
+    {
+    
+        return hermite.hermitecalculate(n, x, _params);
+    }
+    
+    /*************************************************************************
+    Summation of Hermite polynomials using Clenshaw's 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)
+    {
+    
+        return hermite.hermitesum(c, n, x, null);
+    }
+    
+    public static double hermitesum(double[] c, int n, double x, alglib.xparams _params)
+    {
+    
+        return hermite.hermitesum(c, n, x, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void hermitecoefficients(int n, out double[] c, alglib.xparams _params)
+    {
+        c = new double[0];
+        hermite.hermitecoefficients(n, ref c, _params);
+    }
+
+}
+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)
+    {
+    
+        return dawson.dawsonintegral(x, null);
+    }
+    
+    public static double dawsonintegral(double x, alglib.xparams _params)
+    {
+    
+        return dawson.dawsonintegral(x, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void sinecosineintegrals(double x, out double si, out double ci, alglib.xparams _params)
+    {
+        si = 0;
+        ci = 0;
+        trigintegrals.sinecosineintegrals(x, ref si, ref ci, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void hyperbolicsinecosineintegrals(double x, out double shi, out double chi, alglib.xparams _params)
+    {
+        shi = 0;
+        chi = 0;
+        trigintegrals.hyperbolicsinecosineintegrals(x, ref shi, ref chi, _params);
+    }
+
+}
+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)
+    {
+    
+        return poissondistr.poissondistribution(k, m, null);
+    }
+    
+    public static double poissondistribution(int k, double m, alglib.xparams _params)
+    {
+    
+        return poissondistr.poissondistribution(k, m, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return poissondistr.poissoncdistribution(k, m, null);
+    }
+    
+    public static double poissoncdistribution(int k, double m, alglib.xparams _params)
+    {
+    
+        return poissondistr.poissoncdistribution(k, m, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return poissondistr.invpoissondistribution(k, y, null);
+    }
+    
+    public static double invpoissondistribution(int k, double y, alglib.xparams _params)
+    {
+    
+        return poissondistr.invpoissondistribution(k, y, _params);
+    }
+
+}
+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)
+    {
+    
+        return bessel.besselj0(x, null);
+    }
+    
+    public static double besselj0(double x, alglib.xparams _params)
+    {
+    
+        return bessel.besselj0(x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return bessel.besselj1(x, null);
+    }
+    
+    public static double besselj1(double x, alglib.xparams _params)
+    {
+    
+        return bessel.besselj1(x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return bessel.besseljn(n, x, null);
+    }
+    
+    public static double besseljn(int n, double x, alglib.xparams _params)
+    {
+    
+        return bessel.besseljn(n, x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return bessel.bessely0(x, null);
+    }
+    
+    public static double bessely0(double x, alglib.xparams _params)
+    {
+    
+        return bessel.bessely0(x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return bessel.bessely1(x, null);
+    }
+    
+    public static double bessely1(double x, alglib.xparams _params)
+    {
+    
+        return bessel.bessely1(x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return bessel.besselyn(n, x, null);
+    }
+    
+    public static double besselyn(int n, double x, alglib.xparams _params)
+    {
+    
+        return bessel.besselyn(n, x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return bessel.besseli0(x, null);
+    }
+    
+    public static double besseli0(double x, alglib.xparams _params)
+    {
+    
+        return bessel.besseli0(x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return bessel.besseli1(x, null);
+    }
+    
+    public static double besseli1(double x, alglib.xparams _params)
+    {
+    
+        return bessel.besseli1(x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return bessel.besselk0(x, null);
+    }
+    
+    public static double besselk0(double x, alglib.xparams _params)
+    {
+    
+        return bessel.besselk0(x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return bessel.besselk1(x, null);
+    }
+    
+    public static double besselk1(double x, alglib.xparams _params)
+    {
+    
+        return bessel.besselk1(x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return bessel.besselkn(nn, x, null);
+    }
+    
+    public static double besselkn(int nn, double x, alglib.xparams _params)
+    {
+    
+        return bessel.besselkn(nn, x, _params);
+    }
+
+}
+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)
+    {
+    
+        return ibetaf.incompletebeta(a, b, x, null);
+    }
+    
+    public static double incompletebeta(double a, double b, double x, alglib.xparams _params)
+    {
+    
+        return ibetaf.incompletebeta(a, b, x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return ibetaf.invincompletebeta(a, b, y, null);
+    }
+    
+    public static double invincompletebeta(double a, double b, double y, alglib.xparams _params)
+    {
+    
+        return ibetaf.invincompletebeta(a, b, y, _params);
+    }
+
+}
+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)
+    {
+    
+        return fdistr.fdistribution(a, b, x, null);
+    }
+    
+    public static double fdistribution(int a, int b, double x, alglib.xparams _params)
+    {
+    
+        return fdistr.fdistribution(a, b, x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return fdistr.fcdistribution(a, b, x, null);
+    }
+    
+    public static double fcdistribution(int a, int b, double x, alglib.xparams _params)
+    {
+    
+        return fdistr.fcdistribution(a, b, x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return fdistr.invfdistribution(a, b, y, null);
+    }
+    
+    public static double invfdistribution(int a, int b, double y, alglib.xparams _params)
+    {
+    
+        return fdistr.invfdistribution(a, b, y, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void fresnelintegral(double x, ref double c, ref double s, alglib.xparams _params)
+    {
+    
+        fresnel.fresnelintegral(x, ref c, ref s, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void jacobianellipticfunctions(double u, double m, out double sn, out double cn, out double dn, out double ph, alglib.xparams _params)
+    {
+        sn = 0;
+        cn = 0;
+        dn = 0;
+        ph = 0;
+        jacobianelliptic.jacobianellipticfunctions(u, m, ref sn, ref cn, ref dn, ref ph, _params);
+    }
+
+}
+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)
+    {
+    
+        return psif.psi(x, null);
+    }
+    
+    public static double psi(double x, alglib.xparams _params)
+    {
+    
+        return psif.psi(x, _params);
+    }
+
+}
+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)
+    {
+    
+        return expintegrals.exponentialintegralei(x, null);
+    }
+    
+    public static double exponentialintegralei(double x, alglib.xparams _params)
+    {
+    
+        return expintegrals.exponentialintegralei(x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return expintegrals.exponentialintegralen(x, n, null);
+    }
+    
+    public static double exponentialintegralen(double x, int n, alglib.xparams _params)
+    {
+    
+        return expintegrals.exponentialintegralen(x, n, _params);
+    }
+
+}
+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)
+    {
+    
+        return laguerre.laguerrecalculate(n, x, null);
+    }
+    
+    public static double laguerrecalculate(int n, double x, alglib.xparams _params)
+    {
+    
+        return laguerre.laguerrecalculate(n, x, _params);
+    }
+    
+    /*************************************************************************
+    Summation of Laguerre polynomials using Clenshaw's 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)
+    {
+    
+        return laguerre.laguerresum(c, n, x, null);
+    }
+    
+    public static double laguerresum(double[] c, int n, double x, alglib.xparams _params)
+    {
+    
+        return laguerre.laguerresum(c, n, x, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void laguerrecoefficients(int n, out double[] c, alglib.xparams _params)
+    {
+        c = new double[0];
+        laguerre.laguerrecoefficients(n, ref c, _params);
+    }
+
+}
+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)
+    {
+    
+        return chisquaredistr.chisquaredistribution(v, x, null);
+    }
+    
+    public static double chisquaredistribution(double v, double x, alglib.xparams _params)
+    {
+    
+        return chisquaredistr.chisquaredistribution(v, x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return chisquaredistr.chisquarecdistribution(v, x, null);
+    }
+    
+    public static double chisquarecdistribution(double v, double x, alglib.xparams _params)
+    {
+    
+        return chisquaredistr.chisquarecdistribution(v, x, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return chisquaredistr.invchisquaredistribution(v, y, null);
+    }
+    
+    public static double invchisquaredistribution(double v, double y, alglib.xparams _params)
+    {
+    
+        return chisquaredistr.invchisquaredistribution(v, y, _params);
+    }
+
+}
+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)
+    {
+    
+        return legendre.legendrecalculate(n, x, null);
+    }
+    
+    public static double legendrecalculate(int n, double x, alglib.xparams _params)
+    {
+    
+        return legendre.legendrecalculate(n, x, _params);
+    }
+    
+    /*************************************************************************
+    Summation of Legendre polynomials using Clenshaw's 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)
+    {
+    
+        return legendre.legendresum(c, n, x, null);
+    }
+    
+    public static double legendresum(double[] c, int n, double x, alglib.xparams _params)
+    {
+    
+        return legendre.legendresum(c, n, x, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void legendrecoefficients(int n, out double[] c, alglib.xparams _params)
+    {
+        c = new double[0];
+        legendre.legendrecoefficients(n, ref c, _params);
+    }
+
+}
+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)
+    {
+    
+        return betaf.beta(a, b, null);
+    }
+    
+    public static double beta(double a, double b, alglib.xparams _params)
+    {
+    
+        return betaf.beta(a, b, _params);
+    }
+
+}
+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)
+    {
+    
+        return chebyshev.chebyshevcalculate(r, n, x, null);
+    }
+    
+    public static double chebyshevcalculate(int r, int n, double x, alglib.xparams _params)
+    {
+    
+        return chebyshev.chebyshevcalculate(r, n, x, _params);
+    }
+    
+    /*************************************************************************
+    Summation of Chebyshev polynomials using Clenshaw's 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)
+    {
+    
+        return chebyshev.chebyshevsum(c, r, n, x, null);
+    }
+    
+    public static double chebyshevsum(double[] c, int r, int n, double x, alglib.xparams _params)
+    {
+    
+        return chebyshev.chebyshevsum(c, r, n, x, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void chebyshevcoefficients(int n, out double[] c, alglib.xparams _params)
+    {
+        c = new double[0];
+        chebyshev.chebyshevcoefficients(n, ref c, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void fromchebyshev(double[] a, int n, out double[] b, alglib.xparams _params)
+    {
+        b = new double[0];
+        chebyshev.fromchebyshev(a, n, ref b, _params);
+    }
+
+}
+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)
+    {
+    
+        return studenttdistr.studenttdistribution(k, t, null);
+    }
+    
+    public static double studenttdistribution(int k, double t, alglib.xparams _params)
+    {
+    
+        return studenttdistr.studenttdistribution(k, t, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return studenttdistr.invstudenttdistribution(k, p, null);
+    }
+    
+    public static double invstudenttdistribution(int k, double p, alglib.xparams _params)
+    {
+    
+        return studenttdistr.invstudenttdistribution(k, p, _params);
+    }
+
+}
+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)
+    {
+    
+        return binomialdistr.binomialdistribution(k, n, p, null);
+    }
+    
+    public static double binomialdistribution(int k, int n, double p, alglib.xparams _params)
+    {
+    
+        return binomialdistr.binomialdistribution(k, n, p, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return binomialdistr.binomialcdistribution(k, n, p, null);
+    }
+    
+    public static double binomialcdistribution(int k, int n, double p, alglib.xparams _params)
+    {
+    
+        return binomialdistr.binomialcdistribution(k, n, p, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return binomialdistr.invbinomialdistribution(k, n, y, null);
+    }
+    
+    public static double invbinomialdistribution(int k, int n, double y, alglib.xparams _params)
+    {
+    
+        return binomialdistr.invbinomialdistribution(k, n, y, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void airy(double x, out double ai, out double aip, out double bi, out double bip, alglib.xparams _params)
+    {
+        ai = 0;
+        aip = 0;
+        bi = 0;
+        bip = 0;
+        airyf.airy(x, ref ai, ref aip, ref bi, ref bip, _params);
+    }
+
+}
+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,
+            alglib.xparams _params)
+        {
+            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, _params));
+                }
+                else
+                {
+                    z = gammastirf(x, _params);
+                }
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params));
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double p = 0;
+            double q = 0;
+
+            if( (double)(x)<(double)(0) )
+            {
+                result = 2-errorfunctionc(-x, _params);
+                return result;
+            }
+            if( (double)(x)<(double)(0.5) )
+            {
+                result = 1.0-errorfunction(x, _params);
+                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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = 0.5*(errorfunction(x/1.41421356237309504880, _params)+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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = invnormaldistribution(0.5*(e+1), _params)/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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                return result;
+            }
+            ax = a*Math.Log(x)-x-gammafunc.lngamma(a, ref tmp, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                return result;
+            }
+            ax = a*Math.Log(x)-x-gammafunc.lngamma(a, ref tmp, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params)*Math.Sqrt(d);
+            x = a*y*y*y;
+            lgm = gammafunc.lngamma(a, ref tmp, _params);
+            i = 0;
+            while( i<10 )
+            {
+                if( (double)(x)>(double)(x0) || (double)(x)<(double)(x1) )
+                {
+                    d = 0.0625;
+                    break;
+                }
+                y = incompletegammac(a, x, _params);
+                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, _params);
+                    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, _params);
+                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 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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = ellipticintegralkhighprecision(1.0-m, _params);
+            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 Math Library, Release 2.8:  June, 2000
+        Copyright 1984, 1987, 2000 by Stephen L. Moshier
+        *************************************************************************/
+        public static double ellipticintegralkhighprecision(double m1,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+                    }
+                    temp = k-incompleteellipticintegralk(e, m, _params);
+                    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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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, _params);
+                    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, _params);
+            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 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,
+            alglib.xparams _params)
+        {
+            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 Clenshaw's 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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                    chebiterationshichi(a, 2.04326105980879882648E-16, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 1.09896949074905343022E-15, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -1.31313534344092599234E-14, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 5.93976226264314278932E-14, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -3.47197010497749154755E-14, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -1.40059764613117131000E-12, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 9.49044626224223543299E-12, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -1.61596181145435454033E-11, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -1.77899784436430310321E-10, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 1.35455469767246947469E-9, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -1.03257121792819495123E-9, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -3.56699611114982536845E-8, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 1.44818877384267342057E-7, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 7.82018215184051295296E-7, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -5.39919118403805073710E-6, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -3.12458202168959833422E-5, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 8.90136741950727517826E-5, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 2.02558474743846862168E-3, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 2.96064440855633256972E-2, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 1.11847751047257036625E0, ref b0, ref b1, ref b2, _params);
+                    s = k*0.5*(b0-b2);
+                    b0 = -8.12435385225864036372E-18;
+                    b1 = 0.0;
+                    chebiterationshichi(a, 2.17586413290339214377E-17, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 5.22624394924072204667E-17, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -9.48812110591690559363E-16, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 5.35546311647465209166E-15, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -1.21009970113732918701E-14, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -6.00865178553447437951E-14, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 7.16339649156028587775E-13, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -2.93496072607599856104E-12, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -1.40359438136491256904E-12, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 8.76302288609054966081E-11, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -4.40092476213282340617E-10, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -1.87992075640569295479E-10, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 1.31458150989474594064E-8, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -4.75513930924765465590E-8, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -2.21775018801848880741E-7, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 1.94635531373272490962E-6, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 4.33505889257316408893E-6, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -6.13387001076494349496E-5, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, -3.13085477492997465138E-4, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 4.97164789823116062801E-4, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 2.64347496031374526641E-2, ref b0, ref b1, ref b2, _params);
+                    chebiterationshichi(a, 1.11446150876699213025E0, ref b0, ref b1, ref b2, _params);
+                    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, _params);
+                        chebiterationshichi(a, 8.82090135625368160657E-17, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -3.38459811878103047136E-16, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -8.30608026366935789136E-16, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 3.93397875437050071776E-15, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.01765565969729044505E-14, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -4.21128170307640802703E-14, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -1.60818204519802480035E-13, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 3.34714954175994481761E-13, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 2.72600352129153073807E-12, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.66894954752839083608E-12, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -3.49278141024730899554E-11, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -1.58580661666482709598E-10, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -1.79289437183355633342E-10, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.76281629144264523277E-9, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.69050228879421288846E-8, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.25391771228487041649E-7, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.16229947068677338732E-6, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.61038260117376323993E-5, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 3.49810375601053973070E-4, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.28478065259647610779E-2, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.03665722588798326712E0, ref b0, ref b1, ref b2, _params);
+                        s = k*0.5*(b0-b2);
+                        b0 = 8.06913408255155572081E-18;
+                        b1 = 0.0;
+                        chebiterationshichi(a, -2.08074168180148170312E-17, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -5.98111329658272336816E-17, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 2.68533951085945765591E-16, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 4.52313941698904694774E-16, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -3.10734917335299464535E-15, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -4.42823207332531972288E-15, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 3.49639695410806959872E-14, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 6.63406731718911586609E-14, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -3.71902448093119218395E-13, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -1.27135418132338309016E-12, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 2.74851141935315395333E-12, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 2.33781843985453438400E-11, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 2.71436006377612442764E-11, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -2.56600180000355990529E-10, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -1.61021375163803438552E-9, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -4.72543064876271773512E-9, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, -3.00095178028681682282E-9, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 7.79387474390914922337E-8, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.06942765566401507066E-6, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.59503164802313196374E-5, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 3.49592575153777996871E-4, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.28475387530065247392E-2, ref b0, ref b1, ref b2, _params);
+                        chebiterationshichi(a, 1.03665693917934275131E0, ref b0, ref b1, ref b2, _params);
+                        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,
+            alglib.xparams _params)
+        {
+            b2 = b1;
+            b1 = b0;
+            b0 = x*b1-b2+c;
+        }
+
+
+    }
+    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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(k>=0 && (double)(m)>(double)(0), "Domain error in PoissonDistribution");
+            result = igammaf.incompletegammac(k+1, m, _params);
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            alglib.ap.assert(k>=0 && (double)(m)>(double)(0), "Domain error in PoissonDistributionC");
+            result = igammaf.incompletegamma(k+1, m, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            return result;
+        }
+
+
+    }
+    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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                return result;
+            }
+            if( n==1 )
+            {
+                result = sg*besselj1(x, _params);
+                return result;
+            }
+            if( n==2 )
+            {
+                if( (double)(x)==(double)(0) )
+                {
+                    result = 0;
+                }
+                else
+                {
+                    result = sg*(2.0*besselj1(x, _params)/x-besselj0(x, _params));
+                }
+                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, _params)/pk;
+            }
+            else
+            {
+                ans = besselj0(x, _params)/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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params)*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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params)*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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                return result;
+            }
+            if( n==1 )
+            {
+                result = s*bessely1(x, _params);
+                return result;
+            }
+            a = bessely0(x, _params);
+            b = bessely1(x, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                besselmnextcheb(y, 3.33079451882223809783E-17, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -2.43127984654795469359E-16, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 1.71539128555513303061E-15, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -1.16853328779934516808E-14, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 7.67618549860493561688E-14, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -4.85644678311192946090E-13, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 2.95505266312963983461E-12, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -1.72682629144155570723E-11, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 9.67580903537323691224E-11, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -5.18979560163526290666E-10, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 2.65982372468238665035E-9, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -1.30002500998624804212E-8, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 6.04699502254191894932E-8, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -2.67079385394061173391E-7, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 1.11738753912010371815E-6, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -4.41673835845875056359E-6, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 1.64484480707288970893E-5, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -5.75419501008210370398E-5, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 1.88502885095841655729E-4, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -5.76375574538582365885E-4, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 1.63947561694133579842E-3, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -4.32430999505057594430E-3, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 1.05464603945949983183E-2, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -2.37374148058994688156E-2, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 4.93052842396707084878E-2, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -9.49010970480476444210E-2, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 1.71620901522208775349E-1, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -3.04682672343198398683E-1, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 6.76795274409476084995E-1, ref b0, ref b1, ref b2, _params);
+                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, _params);
+            besselmnextcheb(z, -4.83050448594418207126E-18, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 4.46562142029675999901E-17, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 3.46122286769746109310E-17, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, -2.82762398051658348494E-16, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, -3.42548561967721913462E-16, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 1.77256013305652638360E-15, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 3.81168066935262242075E-15, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, -9.55484669882830764870E-15, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, -4.15056934728722208663E-14, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 1.54008621752140982691E-14, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 3.85277838274214270114E-13, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 7.18012445138366623367E-13, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, -1.79417853150680611778E-12, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, -1.32158118404477131188E-11, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, -3.14991652796324136454E-11, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 1.18891471078464383424E-11, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 4.94060238822496958910E-10, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 3.39623202570838634515E-9, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 2.26666899049817806459E-8, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 2.04891858946906374183E-7, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 2.89137052083475648297E-6, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 6.88975834691682398426E-5, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 3.36911647825569408990E-3, ref b0, ref b1, ref b2, _params);
+            besselmnextcheb(z, 8.04490411014108831608E-1, ref b0, ref b1, ref b2, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                besselm1nextcheb(y, -2.11142121435816608115E-17, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 1.55363195773620046921E-16, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.10559694773538630805E-15, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 7.60068429473540693410E-15, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -5.04218550472791168711E-14, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 3.22379336594557470981E-13, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.98397439776494371520E-12, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 1.17361862988909016308E-11, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -6.66348972350202774223E-11, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 3.62559028155211703701E-10, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.88724975172282928790E-9, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 9.38153738649577178388E-9, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -4.44505912879632808065E-8, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 2.00329475355213526229E-7, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -8.56872026469545474066E-7, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 3.47025130813767847674E-6, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.32731636560394358279E-5, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 4.78156510755005422638E-5, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.61760815825896745588E-4, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 5.12285956168575772895E-4, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.51357245063125314899E-3, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 4.15642294431288815669E-3, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.05640848946261981558E-2, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 2.47264490306265168283E-2, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -5.29459812080949914269E-2, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 1.02643658689847095384E-1, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.76416518357834055153E-1, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 2.52587186443633654823E-1, ref b0, ref b1, ref b2, _params);
+                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, _params);
+                besselm1nextcheb(y, 4.41434832307170791151E-18, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -4.65030536848935832153E-17, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -3.20952592199342395980E-17, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 2.96262899764595013876E-16, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 3.30820231092092828324E-16, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.88035477551078244854E-15, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -3.81440307243700780478E-15, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 1.04202769841288027642E-14, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 4.27244001671195135429E-14, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -2.10154184277266431302E-14, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -4.08355111109219731823E-13, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -7.19855177624590851209E-13, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 2.03562854414708950722E-12, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 1.41258074366137813316E-11, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 3.25260358301548823856E-11, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.89749581235054123450E-11, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -5.58974346219658380687E-10, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -3.83538038596423702205E-9, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -2.63146884688951950684E-8, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -2.51223623787020892529E-7, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -3.88256480887769039346E-6, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.10588938762623716291E-4, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -9.76109749136146840777E-3, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 7.78576235018280120474E-1, ref b0, ref b1, ref b2, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                besselmnextcheb(y, 4.25981614279661018399E-14, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 1.03496952576338420167E-11, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 1.90451637722020886025E-9, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 2.53479107902614945675E-7, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 2.28621210311945178607E-5, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 1.26461541144692592338E-3, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 3.59799365153615016266E-2, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, 3.44289899924628486886E-1, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(y, -5.35327393233902768720E-1, ref b0, ref b1, ref b2, _params);
+                v = 0.5*(b0-b2);
+                v = v-Math.Log(0.5*x)*besseli0(x, _params);
+            }
+            else
+            {
+                z = 8.0/x-2.0;
+                besselmfirstcheb(5.30043377268626276149E-18, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, -1.64758043015242134646E-17, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, 5.21039150503902756861E-17, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, -1.67823109680541210385E-16, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, 5.51205597852431940784E-16, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, -1.84859337734377901440E-15, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, 6.34007647740507060557E-15, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, -2.22751332699166985548E-14, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, 8.03289077536357521100E-14, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, -2.98009692317273043925E-13, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, 1.14034058820847496303E-12, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, -4.51459788337394416547E-12, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, 1.85594911495471785253E-11, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, -7.95748924447710747776E-11, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, 3.57739728140030116597E-10, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, -1.69753450938905987466E-9, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, 8.57403401741422608519E-9, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, -4.66048989768794782956E-8, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, 2.76681363944501510342E-7, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, -1.83175552271911948767E-6, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, 1.39498137188764993662E-5, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, -1.28495495816278026384E-4, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, 1.56988388573005337491E-3, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, -3.14481013119645005427E-2, ref b0, ref b1, ref b2, _params);
+                besselmnextcheb(z, 2.44030308206595545468E0, ref b0, ref b1, ref b2, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                besselm1nextcheb(y, -2.42744985051936593393E-15, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -6.66690169419932900609E-13, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.41148839263352776110E-10, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -2.21338763073472585583E-8, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -2.43340614156596823496E-6, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.73028895751305206302E-4, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -6.97572385963986435018E-3, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.22611180822657148235E-1, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -3.53155960776544875667E-1, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 1.52530022733894777053E0, ref b0, ref b1, ref b2, _params);
+                v = 0.5*(b0-b2);
+                result = Math.Log(z)*besseli1(x, _params)+v/x;
+            }
+            else
+            {
+                y = 8.0/x-2.0;
+                besselm1firstcheb(-5.75674448366501715755E-18, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 1.79405087314755922667E-17, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -5.68946255844285935196E-17, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 1.83809354436663880070E-16, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -6.05704724837331885336E-16, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 2.03870316562433424052E-15, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -7.01983709041831346144E-15, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 2.47715442448130437068E-14, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -8.97670518232499435011E-14, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 3.34841966607842919884E-13, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.28917396095102890680E-12, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 5.13963967348173025100E-12, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -2.12996783842756842877E-11, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 9.21831518760500529508E-11, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -4.19035475934189648750E-10, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 2.01504975519703286596E-9, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.03457624656780970260E-8, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 5.74108412545004946722E-8, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -3.50196060308781257119E-7, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 2.40648494783721712015E-6, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -1.93619797416608296024E-5, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 1.95215518471351631108E-4, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, -2.85781685962277938680E-3, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 1.03923736576817238437E-1, ref b0, ref b1, ref b2, _params);
+                besselm1nextcheb(y, 2.72062619048444266945E0, ref b0, ref b1, ref b2, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            b2 = b1;
+            b1 = b0;
+            b0 = x*b1-b2+c;
+        }
+
+
+        private static void besselasympt0(double x,
+            ref double pzero,
+            ref double qzero,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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, _params);
+                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, _params);
+            }
+            else
+            {
+                w = incompletebetafe2(a, b, x, big, biginv, _params)/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, _params)/(gammafunc.gammafunction(a, _params)*gammafunc.gammafunction(b, _params)));
+                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, _params)-gammafunc.lngamma(a, ref sg, _params)-gammafunc.lngamma(b, ref sg, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                        mainlooppos = ihalve;
+                        continue;
+                    }
+                    else
+                    {
+                        dithresh = 1.0e-4;
+                    }
+                    yp = -normaldistr.invnormaldistribution(y, _params);
+                    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, _params);
+                    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, _params);
+                            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, _params);
+                                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, _params)-gammafunc.lngamma(aaa, ref s, _params)-gammafunc.lngamma(bbb, ref s, _params);
+                    i = 0;
+                    mainlooppos = newtcycle;
+                    continue;
+                }
+                
+                //
+                // newtcycle
+                //
+                if( mainlooppos==newtcycle )
+                {
+                    if( i<=7 )
+                    {
+                        if( i!=0 )
+                        {
+                            yyy = incompletebeta(aaa, bbb, x, _params);
+                        }
+                        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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params)/(gammafunc.gammafunction(a, _params)*gammafunc.gammafunction(b, _params));
+                s = s*t*Math.Pow(x, a);
+            }
+            else
+            {
+                t = gammafunc.lngamma(a+b, ref sg, _params)-gammafunc.lngamma(a, ref sg, _params)-gammafunc.lngamma(b, ref sg, _params)+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 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            
+            //
+            // 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, _params);
+                result = (b-b*w)/(a*w);
+            }
+            else
+            {
+                w = ibetaf.invincompletebeta(0.5*a, 0.5*b, 1.0-y, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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 Clenshaw's 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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            return result;
+        }
+
+
+    }
+    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,
+            alglib.xparams _params)
+        {
+            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 Clenshaw's 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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                sg = sg*s;
+                y = gammafunc.lngamma(b, ref s, _params)-y;
+                sg = sg*s;
+                y = gammafunc.lngamma(a, ref s, _params)+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, _params);
+            alglib.ap.assert((double)(y)!=(double)(0), "Overflow in Beta");
+            if( (double)(a)>(double)(b) )
+            {
+                y = gammafunc.gammafunction(a, _params)/y;
+                y = y*gammafunc.gammafunction(b, _params);
+            }
+            else
+            {
+                y = gammafunc.gammafunction(b, _params)/y;
+                y = y*gammafunc.gammafunction(a, _params);
+            }
+            result = y;
+            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,
+            alglib.xparams _params)
+        {
+            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 Clenshaw's 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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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), _params);
+                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, _params);
+            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 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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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, _params), _params);
+                }
+                else
+                {
+                    dk = 1.0-Math.Pow(1.0-p, dn);
+                }
+            }
+            else
+            {
+                dk = k+1;
+                dk = ibetaf.incompletebeta(dk, dn, p, _params);
+            }
+            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,
+            alglib.xparams _params)
+        {
+            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, _params)/dn, _params);
+                }
+                else
+                {
+                    p = 1.0-Math.Pow(y, 1.0/dn);
+                }
+            }
+            else
+            {
+                dk = k+1;
+                p = ibetaf.incompletebeta(dn, dk, 0.5, _params);
+                if( (double)(p)>(double)(0.5) )
+                {
+                    p = ibetaf.invincompletebeta(dk, dn, 1.0-y, _params);
+                }
+                else
+                {
+                    p = 1.0-ibetaf.invincompletebeta(dn, dk, y, _params);
+                }
+            }
+            result = p;
+            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,
+            alglib.xparams _params)
+        {
+            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);
+            }
+        }
+
+
+    }
+}
+
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/statistics.cs
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/statistics.cs	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/ALGLIB-3.15.0/statistics.cs	(revision 17158)
@@ -0,0 +1,18652 @@
+/*************************************************************************
+ALGLIB 3.15.0 (source code generated 2019-02-20)
+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 164
+#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).
+
+    NOTE: variance is calculated by dividing sum of squares by N-1, not N.
+
+      -- 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, null);
+    }
+    
+    public static void samplemoments(double[] x, int n, out double mean, out double variance, out double skewness, out double kurtosis, alglib.xparams _params)
+    {
+        mean = 0;
+        variance = 0;
+        skewness = 0;
+        kurtosis = 0;
+        basestat.samplemoments(x, n, ref mean, ref variance, ref skewness, ref kurtosis, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void samplemoments(double[] x, out double mean, out double variance, out double skewness, out double kurtosis, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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)
+    {
+    
+        return basestat.samplemean(x, n, null);
+    }
+    
+    public static double samplemean(double[] x, int n, alglib.xparams _params)
+    {
+    
+        return basestat.samplemean(x, n, _params);
+    }
+            
+    public static double samplemean(double[] x)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        double result = basestat.samplemean(x, n, null);
+    
+        return result;
+    }
+            
+    public static double samplemean(double[] x, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        double result = basestat.samplemean(x, n, _params);
+    
+        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)
+    {
+    
+        return basestat.samplevariance(x, n, null);
+    }
+    
+    public static double samplevariance(double[] x, int n, alglib.xparams _params)
+    {
+    
+        return basestat.samplevariance(x, n, _params);
+    }
+            
+    public static double samplevariance(double[] x)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        double result = basestat.samplevariance(x, n, null);
+    
+        return result;
+    }
+            
+    public static double samplevariance(double[] x, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        double result = basestat.samplevariance(x, n, _params);
+    
+        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)
+    {
+    
+        return basestat.sampleskewness(x, n, null);
+    }
+    
+    public static double sampleskewness(double[] x, int n, alglib.xparams _params)
+    {
+    
+        return basestat.sampleskewness(x, n, _params);
+    }
+            
+    public static double sampleskewness(double[] x)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        double result = basestat.sampleskewness(x, n, null);
+    
+        return result;
+    }
+            
+    public static double sampleskewness(double[] x, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        double result = basestat.sampleskewness(x, n, _params);
+    
+        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)
+    {
+    
+        return basestat.samplekurtosis(x, n, null);
+    }
+    
+    public static double samplekurtosis(double[] x, int n, alglib.xparams _params)
+    {
+    
+        return basestat.samplekurtosis(x, n, _params);
+    }
+            
+    public static double samplekurtosis(double[] x)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        double result = basestat.samplekurtosis(x, n, null);
+    
+        return result;
+    }
+            
+    public static double samplekurtosis(double[] x, alglib.xparams _params)
+    {
+        int n;
+    
+    
+        n = ap.len(x);
+        double result = basestat.samplekurtosis(x, n, _params);
+    
+        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, null);
+    }
+    
+    public static void sampleadev(double[] x, int n, out double adev, alglib.xparams _params)
+    {
+        adev = 0;
+        basestat.sampleadev(x, n, ref adev, _params);
+    }
+            
+    public static void sampleadev(double[] x, out double adev)
+    {
+        int n;
+    
+        adev = 0;
+        n = ap.len(x);
+        basestat.sampleadev(x, n, ref adev, null);
+    
+        return;
+    }
+            
+    public static void sampleadev(double[] x, out double adev, alglib.xparams _params)
+    {
+        int n;
+    
+        adev = 0;
+        n = ap.len(x);
+        basestat.sampleadev(x, n, ref adev, _params);
+    
+        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, null);
+    }
+    
+    public static void samplemedian(double[] x, int n, out double median, alglib.xparams _params)
+    {
+        median = 0;
+        basestat.samplemedian(x, n, ref median, _params);
+    }
+            
+    public static void samplemedian(double[] x, out double median)
+    {
+        int n;
+    
+        median = 0;
+        n = ap.len(x);
+        basestat.samplemedian(x, n, ref median, null);
+    
+        return;
+    }
+            
+    public static void samplemedian(double[] x, out double median, alglib.xparams _params)
+    {
+        int n;
+    
+        median = 0;
+        n = ap.len(x);
+        basestat.samplemedian(x, n, ref median, _params);
+    
+        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, null);
+    }
+    
+    public static void samplepercentile(double[] x, int n, double p, out double v, alglib.xparams _params)
+    {
+        v = 0;
+        basestat.samplepercentile(x, n, p, ref v, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void samplepercentile(double[] x, double p, out double v, alglib.xparams _params)
+    {
+        int n;
+    
+        v = 0;
+        n = ap.len(x);
+        basestat.samplepercentile(x, n, p, ref v, _params);
+    
+        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)
+    {
+    
+        return basestat.cov2(x, y, n, null);
+    }
+    
+    public static double cov2(double[] x, double[] y, int n, alglib.xparams _params)
+    {
+    
+        return basestat.cov2(x, y, n, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static double cov2(double[] x, double[] y, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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)
+    {
+    
+        return basestat.pearsoncorr2(x, y, n, null);
+    }
+    
+    public static double pearsoncorr2(double[] x, double[] y, int n, alglib.xparams _params)
+    {
+    
+        return basestat.pearsoncorr2(x, y, n, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static double pearsoncorr2(double[] x, double[] y, alglib.xparams _params)
+    {
+        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, _params);
+    
+        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)
+    {
+    
+        return basestat.spearmancorr2(x, y, n, null);
+    }
+    
+    public static double spearmancorr2(double[] x, double[] y, int n, alglib.xparams _params)
+    {
+    
+        return basestat.spearmancorr2(x, y, n, _params);
+    }
+            
+    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, null);
+    
+        return result;
+    }
+            
+    public static double spearmancorr2(double[] x, double[] y, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return result;
+    }
+    
+    /*************************************************************************
+    Covariance matrix
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void covm(double[,] x, int n, int m, out double[,] c, alglib.xparams _params)
+    {
+        c = new double[0,0];
+        basestat.covm(x, n, m, ref c, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void covm(double[,] x, out double[,] c, alglib.xparams _params)
+    {
+        int n;
+        int m;
+    
+        c = new double[0,0];
+        n = ap.rows(x);
+        m = ap.cols(x);
+        basestat.covm(x, n, m, ref c, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Pearson product-moment correlation matrix
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void pearsoncorrm(double[,] x, int n, int m, out double[,] c, alglib.xparams _params)
+    {
+        c = new double[0,0];
+        basestat.pearsoncorrm(x, n, m, ref c, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void pearsoncorrm(double[,] x, out double[,] c, alglib.xparams _params)
+    {
+        int n;
+        int m;
+    
+        c = new double[0,0];
+        n = ap.rows(x);
+        m = ap.cols(x);
+        basestat.pearsoncorrm(x, n, m, ref c, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Spearman's rank correlation matrix
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void spearmancorrm(double[,] x, int n, int m, out double[,] c, alglib.xparams _params)
+    {
+        c = new double[0,0];
+        basestat.spearmancorrm(x, n, m, ref c, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spearmancorrm(double[,] x, out double[,] c, alglib.xparams _params)
+    {
+        int n;
+        int m;
+    
+        c = new double[0,0];
+        n = ap.rows(x);
+        m = ap.cols(x);
+        basestat.spearmancorrm(x, n, m, ref c, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Cross-covariance matrix
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void covm2(double[,] x, double[,] y, int n, int m1, int m2, out double[,] c, alglib.xparams _params)
+    {
+        c = new double[0,0];
+        basestat.covm2(x, y, n, m1, m2, ref c, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void covm2(double[,] x, double[,] y, out double[,] c, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Pearson product-moment cross-correlation matrix
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void pearsoncorrm2(double[,] x, double[,] y, int n, int m1, int m2, out double[,] c, alglib.xparams _params)
+    {
+        c = new double[0,0];
+        basestat.pearsoncorrm2(x, y, n, m1, m2, ref c, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void pearsoncorrm2(double[,] x, double[,] y, out double[,] c, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    Spearman's rank cross-correlation matrix
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      ! * hardware vendor (Intel) implementations of linear algebra primitives
+      !   (C++ and C# versions, x86/x64 platform)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    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, null);
+    }
+    
+    public static void spearmancorrm2(double[,] x, double[,] y, int n, int m1, int m2, out double[,] c, alglib.xparams _params)
+    {
+        c = new double[0,0];
+        basestat.spearmancorrm2(x, y, n, m1, m2, ref c, _params);
+    }
+            
+    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, null);
+    
+        return;
+    }
+            
+    public static void spearmancorrm2(double[,] x, double[,] y, out double[,] c, alglib.xparams _params)
+    {
+        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, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function replaces data in XY by their ranks:
+    * XY is processed row-by-row
+    * rows are processed separately
+    * tied data are correctly handled (tied ranks are calculated)
+    * ranking starts from 0, ends at NFeatures-1
+    * sum of within-row values is equal to (NFeatures-1)*NFeatures/2
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        XY      -   array[NPoints,NFeatures], dataset
+        NPoints -   number of points
+        NFeatures-  number of features
+
+    OUTPUT PARAMETERS:
+        XY      -   data are replaced by their within-row ranks;
+                    ranking starts from 0, ends at NFeatures-1
+
+      -- ALGLIB --
+         Copyright 18.04.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rankdata(ref double[,] xy, int npoints, int nfeatures)
+    {
+    
+        basestat.rankdata(xy, npoints, nfeatures, null);
+    }
+    
+    public static void rankdata(ref double[,] xy, int npoints, int nfeatures, alglib.xparams _params)
+    {
+    
+        basestat.rankdata(xy, npoints, nfeatures, _params);
+    }
+            
+    public static void rankdata(ref double[,] xy)
+    {
+        int npoints;
+        int nfeatures;
+    
+    
+        npoints = ap.rows(xy);
+        nfeatures = ap.cols(xy);
+        basestat.rankdata(xy, npoints, nfeatures, null);
+    
+        return;
+    }
+            
+    public static void rankdata(ref double[,] xy, alglib.xparams _params)
+    {
+        int npoints;
+        int nfeatures;
+    
+    
+        npoints = ap.rows(xy);
+        nfeatures = ap.cols(xy);
+        basestat.rankdata(xy, npoints, nfeatures, _params);
+    
+        return;
+    }
+    
+    /*************************************************************************
+    This function replaces data in XY by their CENTERED ranks:
+    * XY is processed row-by-row
+    * rows are processed separately
+    * tied data are correctly handled (tied ranks are calculated)
+    * centered ranks are just usual ranks, but centered in such way  that  sum
+      of within-row values is equal to 0.0.
+    * centering is performed by subtracting mean from each row, i.e it changes
+      mean value, but does NOT change higher moments
+
+      ! COMMERCIAL EDITION OF ALGLIB:
+      !
+      ! Commercial Edition of ALGLIB includes following important improvements
+      ! of this function:
+      ! * high-performance native backend with same C# interface (C# version)
+      ! * multithreading support (C++ and C# versions)
+      !
+      ! We recommend you to read 'Working with commercial version' section  of
+      ! ALGLIB Reference Manual in order to find out how to  use  performance-
+      ! related features provided by commercial edition of ALGLIB.
+
+    INPUT PARAMETERS:
+        XY      -   array[NPoints,NFeatures], dataset
+        NPoints -   number of points
+        NFeatures-  number of features
+
+    OUTPUT PARAMETERS:
+        XY      -   data are replaced by their within-row ranks;
+                    ranking starts from 0, ends at NFeatures-1
+
+      -- ALGLIB --
+         Copyright 18.04.2013 by Bochkanov Sergey
+    *************************************************************************/
+    public static void rankdatacentered(ref double[,] xy, int npoints, int nfeatures)
+    {
+    
+        basestat.rankdatacentered(xy, npoints, nfeatures, null);
+    }
+    
+    public static void rankdatacentered(ref double[,] xy, int npoints, int nfeatures, alglib.xparams _params)
+    {
+    
+        basestat.rankdatacentered(xy, npoints, nfeatures, _params);
+    }
+            
+    public static void rankdatacentered(ref double[,] xy)
+    {
+        int npoints;
+        int nfeatures;
+    
+    
+        npoints = ap.rows(xy);
+        nfeatures = ap.cols(xy);
+        basestat.rankdatacentered(xy, npoints, nfeatures, null);
+    
+        return;
+    }
+            
+    public static void rankdatacentered(ref double[,] xy, alglib.xparams _params)
+    {
+        int npoints;
+        int nfeatures;
+    
+    
+        npoints = ap.rows(xy);
+        nfeatures = ap.cols(xy);
+        basestat.rankdatacentered(xy, npoints, nfeatures, _params);
+    
+        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)
+    {
+    
+        return basestat.pearsoncorrelation(x, y, n, null);
+    }
+    
+    public static double pearsoncorrelation(double[] x, double[] y, int n, alglib.xparams _params)
+    {
+    
+        return basestat.pearsoncorrelation(x, y, n, _params);
+    }
+    
+    /*************************************************************************
+    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)
+    {
+    
+        return basestat.spearmanrankcorrelation(x, y, n, null);
+    }
+    
+    public static double spearmanrankcorrelation(double[] x, double[] y, int n, alglib.xparams _params)
+    {
+    
+        return basestat.spearmanrankcorrelation(x, y, n, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void wilcoxonsignedranktest(double[] x, int n, double e, out double bothtails, out double lefttail, out double righttail, alglib.xparams _params)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        wsr.wilcoxonsignedranktest(x, n, e, ref bothtails, ref lefttail, ref righttail, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void onesamplesigntest(double[] x, int n, double median, out double bothtails, out double lefttail, out double righttail, alglib.xparams _params)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        stest.onesamplesigntest(x, n, median, ref bothtails, ref lefttail, ref righttail, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void pearsoncorrelationsignificance(double r, int n, out double bothtails, out double lefttail, out double righttail, alglib.xparams _params)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        correlationtests.pearsoncorrelationsignificance(r, n, ref bothtails, ref lefttail, ref righttail, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void spearmanrankcorrelationsignificance(double r, int n, out double bothtails, out double lefttail, out double righttail, alglib.xparams _params)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        correlationtests.spearmanrankcorrelationsignificance(r, n, ref bothtails, ref lefttail, ref righttail, _params);
+    }
+
+}
+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, N>=0
+        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.
+
+    NOTE: this function correctly handles degenerate cases:
+          * when N=0, all p-values are set to 1.0
+          * when variance of X[] is exactly zero, p-values are set
+            to 1.0 or 0.0, depending on difference between sample mean and
+            value of mean being tested.
+
+
+      -- 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, null);
+    }
+    
+    public static void studentttest1(double[] x, int n, double mean, out double bothtails, out double lefttail, out double righttail, alglib.xparams _params)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        studentttests.studentttest1(x, n, mean, ref bothtails, ref lefttail, ref righttail, _params);
+    }
+    
+    /*************************************************************************
+    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.
+
+    NOTE: this function correctly handles degenerate cases:
+          * when N=0 or M=0, all p-values are set to 1.0
+          * when both samples has exactly zero variance, p-values are set
+            to 1.0 or 0.0, depending on difference between means.
+
+      -- 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, null);
+    }
+    
+    public static void studentttest2(double[] x, int n, double[] y, int m, out double bothtails, out double lefttail, out double righttail, alglib.xparams _params)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        studentttests.studentttest2(x, n, y, m, ref bothtails, ref lefttail, ref righttail, _params);
+    }
+    
+    /*************************************************************************
+    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.
+    Equality of variances 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.
+
+    NOTE: this function correctly handles degenerate cases:
+          * when N=0 or M=0, all p-values are set to 1.0
+          * when both samples has zero variance, p-values are set
+            to 1.0 or 0.0, depending on difference between means.
+          * when only one sample has zero variance, test reduces to 1-sample
+            version.
+
+      -- 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, null);
+    }
+    
+    public static void unequalvariancettest(double[] x, int n, double[] y, int m, out double bothtails, out double lefttail, out double righttail, alglib.xparams _params)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        studentttests.unequalvariancettest(x, n, y, m, ref bothtails, ref lefttail, ref righttail, _params);
+    }
+
+}
+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].
+
+    NOTE: P-value approximation was  optimized  for  0.0001<=p<=0.2500.  Thus,
+          P's outside of this interval are enforced to these bounds. Say,  you
+          may quite often get P equal to exactly 0.25 or 0.0001.
+
+      -- 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, null);
+    }
+    
+    public static void mannwhitneyutest(double[] x, int n, double[] y, int m, out double bothtails, out double lefttail, out double righttail, alglib.xparams _params)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        mannwhitneyu.mannwhitneyutest(x, n, y, m, ref bothtails, ref lefttail, ref righttail, _params);
+    }
+
+}
+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:
+        P           -   p-value for the test
+
+    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, null);
+    }
+    
+    public static void jarqueberatest(double[] x, int n, out double p, alglib.xparams _params)
+    {
+        p = 0;
+        jarquebera.jarqueberatest(x, n, ref p, _params);
+    }
+
+}
+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, null);
+    }
+    
+    public static void ftest(double[] x, int n, double[] y, int m, out double bothtails, out double lefttail, out double righttail, alglib.xparams _params)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        variancetests.ftest(x, n, y, m, ref bothtails, ref lefttail, ref righttail, _params);
+    }
+    
+    /*************************************************************************
+    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, null);
+    }
+    
+    public static void onesamplevariancetest(double[] x, int n, double variance, out double bothtails, out double lefttail, out double righttail, alglib.xparams _params)
+    {
+        bothtails = 0;
+        lefttail = 0;
+        righttail = 0;
+        variancetests.onesamplevariancetest(x, n, variance, ref bothtails, ref lefttail, ref righttail, _params);
+    }
+
+}
+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).
+
+        NOTE: variance is calculated by dividing sum of squares by N-1, not N.
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params), "Cov2: X is not finite vector");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params), "PearsonCorr2: X is not finite vector");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "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,
+            alglib.xparams _params)
+        {
+            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, _params), "SpearmanCorr2: X is not finite vector");
+            alglib.ap.assert(apserv.isfinitevector(y, n, _params), "SpearmanCorr2: Y is not finite vector");
+            
+            //
+            // Special case
+            //
+            if( n<=1 )
+            {
+                result = 0;
+                return result;
+            }
+            basicstatops.rankx(x, n, false, buf, _params);
+            basicstatops.rankx(y, n, false, buf, _params);
+            result = pearsoncorr2(x, y, n, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Covariance matrix
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, c, 0, 0, true, _params);
+            ablas.rmatrixenforcesymmetricity(c, m, true, _params);
+        }
+
+
+        /*************************************************************************
+        Pearson product-moment correlation matrix
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            double[] t = new double[0];
+            int i = 0;
+            int j = 0;
+            double v = 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, _params), "PearsonCorrM: X contains infinite/NAN elements");
+            t = new double[m];
+            covm(x, n, m, ref c, _params);
+            for(i=0; i<=m-1; i++)
+            {
+                if( (double)(c[i,i])>(double)(0) )
+                {
+                    t[i] = 1/Math.Sqrt(c[i,i]);
+                }
+                else
+                {
+                    t[i] = 0.0;
+                }
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                v = t[i];
+                for(j=0; j<=m-1; j++)
+                {
+                    c[i,j] = c[i,j]*v*t[j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Spearman's rank correlation matrix
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            apserv.apbuffers buf = new apserv.apbuffers();
+            double[,] xc = new double[0,0];
+            double[] t = new double[0];
+            double v = 0;
+            double vv = 0;
+            double x0 = 0;
+            bool b = new bool();
+
+            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, _params), "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)];
+            c = new double[m, m];
+            
+            //
+            // Replace data with ranks
+            //
+            xc = new double[m, n];
+            ablas.rmatrixtranspose(n, m, x, 0, 0, xc, 0, 0, _params);
+            rankdata(xc, m, n, _params);
+            
+            //
+            // 1. Calculate means, check for constant columns
+            // 2. Center variables, constant  columns are
+            //   artificialy zeroed (they must be zero in exact arithmetics,
+            //   but unfortunately floating point is not exact).
+            //
+            for(i=0; i<=m-1; i++)
+            {
+                
+                //
+                // Calculate:
+                // * V - mean value of I-th variable
+                // * B - True in case all variable values are same
+                //
+                v = 0;
+                b = true;
+                x0 = xc[i,0];
+                for(j=0; j<=n-1; j++)
+                {
+                    vv = xc[i,j];
+                    v = v+vv;
+                    b = b && (double)(vv)==(double)(x0);
+                }
+                v = v/n;
+                
+                //
+                // Center/zero I-th variable
+                //
+                if( b )
+                {
+                    
+                    //
+                    // Zero
+                    //
+                    for(j=0; j<=n-1; j++)
+                    {
+                        xc[i,j] = 0.0;
+                    }
+                }
+                else
+                {
+                    
+                    //
+                    // Center
+                    //
+                    for(j=0; j<=n-1; j++)
+                    {
+                        xc[i,j] = xc[i,j]-v;
+                    }
+                }
+            }
+            
+            //
+            // Calculate upper half of symmetric covariance matrix
+            //
+            ablas.rmatrixsyrk(m, n, (double)1/(double)(n-1), xc, 0, 0, 0, 0.0, c, 0, 0, true, _params);
+            
+            //
+            // Calculate Pearson coefficients (upper triangle)
+            //
+            for(i=0; i<=m-1; i++)
+            {
+                if( (double)(c[i,i])>(double)(0) )
+                {
+                    t[i] = 1/Math.Sqrt(c[i,i]);
+                }
+                else
+                {
+                    t[i] = 0.0;
+                }
+            }
+            for(i=0; i<=m-1; i++)
+            {
+                v = t[i];
+                for(j=i; j<=m-1; j++)
+                {
+                    c[i,j] = c[i,j]*v*t[j];
+                }
+            }
+            
+            //
+            // force symmetricity
+            //
+            ablas.rmatrixenforcesymmetricity(c, m, true, _params);
+        }
+
+
+        /*************************************************************************
+        Cross-covariance matrix
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, c, 0, 0, _params);
+        }
+
+
+        /*************************************************************************
+        Pearson product-moment cross-correlation matrix
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params), "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, _params), "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, c, 0, 0, _params);
+            
+            //
+            // Divide by standard deviations
+            //
+            for(i=0; i<=m1-1; i++)
+            {
+                if( (double)(sx[i])!=(double)(0) )
+                {
+                    sx[i] = 1/sx[i];
+                }
+                else
+                {
+                    sx[i] = 0.0;
+                }
+            }
+            for(i=0; i<=m2-1; i++)
+            {
+                if( (double)(sy[i])!=(double)(0) )
+                {
+                    sy[i] = 1/sy[i];
+                }
+                else
+                {
+                    sy[i] = 0.0;
+                }
+            }
+            for(i=0; i<=m1-1; i++)
+            {
+                v = sx[i];
+                for(j=0; j<=m2-1; j++)
+                {
+                    c[i,j] = c[i,j]*v*sy[j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Spearman's rank cross-correlation matrix
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! * hardware vendor (Intel) implementations of linear algebra primitives
+          !   (C++ and C# versions, x86/x64 platform)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int j = 0;
+            double v = 0;
+            double v2 = 0;
+            double vv = 0;
+            bool b = new bool();
+            double[] t = new double[0];
+            double x0 = 0;
+            double y0 = 0;
+            double[] sx = new double[0];
+            double[] sy = new double[0];
+            double[,] xc = new double[0,0];
+            double[,] yc = new double[0,0];
+            apserv.apbuffers buf = new apserv.apbuffers();
+
+            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, _params), "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, _params), "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)];
+            sx = new double[m1];
+            sy = new double[m2];
+            c = new double[m1, m2];
+            
+            //
+            // Replace data with ranks
+            //
+            xc = new double[m1, n];
+            yc = new double[m2, n];
+            ablas.rmatrixtranspose(n, m1, x, 0, 0, xc, 0, 0, _params);
+            ablas.rmatrixtranspose(n, m2, y, 0, 0, yc, 0, 0, _params);
+            rankdata(xc, m1, n, _params);
+            rankdata(yc, m2, n, _params);
+            
+            //
+            // 1. Calculate means, variances, check for constant columns
+            // 2. Center variables, constant  columns are
+            //   artificialy zeroed (they must be zero in exact arithmetics,
+            //   but unfortunately floating point is not exact).
+            //
+            // Description of variables:
+            // * V - mean value of I-th variable
+            // * V2- variance
+            // * VV-temporary
+            // * B - True in case all variable values are same
+            //
+            for(i=0; i<=m1-1; i++)
+            {
+                v = 0;
+                v2 = 0.0;
+                b = true;
+                x0 = xc[i,0];
+                for(j=0; j<=n-1; j++)
+                {
+                    vv = xc[i,j];
+                    v = v+vv;
+                    b = b && (double)(vv)==(double)(x0);
+                }
+                v = v/n;
+                if( b )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        xc[i,j] = 0.0;
+                    }
+                }
+                else
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        vv = xc[i,j];
+                        xc[i,j] = vv-v;
+                        v2 = v2+(vv-v)*(vv-v);
+                    }
+                }
+                sx[i] = Math.Sqrt(v2/(n-1));
+            }
+            for(i=0; i<=m2-1; i++)
+            {
+                v = 0;
+                v2 = 0.0;
+                b = true;
+                y0 = yc[i,0];
+                for(j=0; j<=n-1; j++)
+                {
+                    vv = yc[i,j];
+                    v = v+vv;
+                    b = b && (double)(vv)==(double)(y0);
+                }
+                v = v/n;
+                if( b )
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        yc[i,j] = 0.0;
+                    }
+                }
+                else
+                {
+                    for(j=0; j<=n-1; j++)
+                    {
+                        vv = yc[i,j];
+                        yc[i,j] = vv-v;
+                        v2 = v2+(vv-v)*(vv-v);
+                    }
+                }
+                sy[i] = Math.Sqrt(v2/(n-1));
+            }
+            
+            //
+            // calculate cross-covariance matrix
+            //
+            ablas.rmatrixgemm(m1, m2, n, (double)1/(double)(n-1), xc, 0, 0, 0, yc, 0, 0, 1, 0.0, c, 0, 0, _params);
+            
+            //
+            // Divide by standard deviations
+            //
+            for(i=0; i<=m1-1; i++)
+            {
+                if( (double)(sx[i])!=(double)(0) )
+                {
+                    sx[i] = 1/sx[i];
+                }
+                else
+                {
+                    sx[i] = 0.0;
+                }
+            }
+            for(i=0; i<=m2-1; i++)
+            {
+                if( (double)(sy[i])!=(double)(0) )
+                {
+                    sy[i] = 1/sy[i];
+                }
+                else
+                {
+                    sy[i] = 0.0;
+                }
+            }
+            for(i=0; i<=m1-1; i++)
+            {
+                v = sx[i];
+                for(j=0; j<=m2-1; j++)
+                {
+                    c[i,j] = c[i,j]*v*sy[j];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        This function replaces data in XY by their ranks:
+        * XY is processed row-by-row
+        * rows are processed separately
+        * tied data are correctly handled (tied ranks are calculated)
+        * ranking starts from 0, ends at NFeatures-1
+        * sum of within-row values is equal to (NFeatures-1)*NFeatures/2
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            XY      -   array[NPoints,NFeatures], dataset
+            NPoints -   number of points
+            NFeatures-  number of features
+
+        OUTPUT PARAMETERS:
+            XY      -   data are replaced by their within-row ranks;
+                        ranking starts from 0, ends at NFeatures-1
+
+          -- ALGLIB --
+             Copyright 18.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rankdata(double[,] xy,
+            int npoints,
+            int nfeatures,
+            alglib.xparams _params)
+        {
+            apserv.apbuffers buf0 = new apserv.apbuffers();
+            apserv.apbuffers buf1 = new apserv.apbuffers();
+            int basecasecost = 0;
+            alglib.smp.shared_pool pool = new alglib.smp.shared_pool();
+
+            alglib.ap.assert(npoints>=0, "RankData: NPoints<0");
+            alglib.ap.assert(nfeatures>=1, "RankData: NFeatures<1");
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "RankData: Rows(XY)<NPoints");
+            alglib.ap.assert(alglib.ap.cols(xy)>=nfeatures || npoints==0, "RankData: Cols(XY)<NFeatures");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, npoints, nfeatures, _params), "RankData: XY contains infinite/NAN elements");
+            
+            //
+            // Basecase cost is a maximum cost of basecase problems.
+            // Problems harded than that cost will be split.
+            //
+            // Problem cost is assumed to be NPoints*NFeatures*log2(NFeatures),
+            // which is proportional, but NOT equal to number of FLOPs required
+            // to solve problem.
+            //
+            // Try to use serial code for basecase problems, no SMP functionality, no shared pools.
+            //
+            basecasecost = 10000;
+            if( (double)(apserv.rmul3(npoints, nfeatures, apserv.logbase2(nfeatures, _params), _params))<(double)(basecasecost) )
+            {
+                rankdatabasecase(xy, 0, npoints, nfeatures, false, buf0, buf1, _params);
+                return;
+            }
+            
+            //
+            // Parallel code
+            //
+            alglib.smp.ae_shared_pool_set_seed(pool, buf0);
+            rankdatarec(xy, 0, npoints, nfeatures, false, pool, basecasecost, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_rankdata(double[,] xy,
+            int npoints,
+            int nfeatures, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        This function replaces data in XY by their CENTERED ranks:
+        * XY is processed row-by-row
+        * rows are processed separately
+        * tied data are correctly handled (tied ranks are calculated)
+        * centered ranks are just usual ranks, but centered in such way  that  sum
+          of within-row values is equal to 0.0.
+        * centering is performed by subtracting mean from each row, i.e it changes
+          mean value, but does NOT change higher moments
+
+          ! COMMERCIAL EDITION OF ALGLIB:
+          ! 
+          ! Commercial Edition of ALGLIB includes following important improvements
+          ! of this function:
+          ! * high-performance native backend with same C# interface (C# version)
+          ! * multithreading support (C++ and C# versions)
+          ! 
+          ! We recommend you to read 'Working with commercial version' section  of
+          ! ALGLIB Reference Manual in order to find out how to  use  performance-
+          ! related features provided by commercial edition of ALGLIB.
+
+        INPUT PARAMETERS:
+            XY      -   array[NPoints,NFeatures], dataset
+            NPoints -   number of points
+            NFeatures-  number of features
+
+        OUTPUT PARAMETERS:
+            XY      -   data are replaced by their within-row ranks;
+                        ranking starts from 0, ends at NFeatures-1
+
+          -- ALGLIB --
+             Copyright 18.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        public static void rankdatacentered(double[,] xy,
+            int npoints,
+            int nfeatures,
+            alglib.xparams _params)
+        {
+            apserv.apbuffers buf0 = new apserv.apbuffers();
+            apserv.apbuffers buf1 = new apserv.apbuffers();
+            int basecasecost = 0;
+            alglib.smp.shared_pool pool = new alglib.smp.shared_pool();
+
+            alglib.ap.assert(npoints>=0, "RankData: NPoints<0");
+            alglib.ap.assert(nfeatures>=1, "RankData: NFeatures<1");
+            alglib.ap.assert(alglib.ap.rows(xy)>=npoints, "RankData: Rows(XY)<NPoints");
+            alglib.ap.assert(alglib.ap.cols(xy)>=nfeatures || npoints==0, "RankData: Cols(XY)<NFeatures");
+            alglib.ap.assert(apserv.apservisfinitematrix(xy, npoints, nfeatures, _params), "RankData: XY contains infinite/NAN elements");
+            
+            //
+            // Basecase cost is a maximum cost of basecase problems.
+            // Problems harded than that cost will be split.
+            //
+            // Problem cost is assumed to be NPoints*NFeatures*log2(NFeatures),
+            // which is proportional, but NOT equal to number of FLOPs required
+            // to solve problem.
+            //
+            // Try to use serial code, no SMP functionality, no shared pools.
+            //
+            basecasecost = 10000;
+            if( (double)(apserv.rmul3(npoints, nfeatures, apserv.logbase2(nfeatures, _params), _params))<(double)(basecasecost) )
+            {
+                rankdatabasecase(xy, 0, npoints, nfeatures, true, buf0, buf1, _params);
+                return;
+            }
+            
+            //
+            // Parallel code
+            //
+            alglib.smp.ae_shared_pool_set_seed(pool, buf0);
+            rankdatarec(xy, 0, npoints, nfeatures, true, pool, basecasecost, _params);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_rankdatacentered(double[,] xy,
+            int npoints,
+            int nfeatures, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = pearsoncorr2(x, y, n, _params);
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            result = spearmancorr2(x, y, n, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Recurrent code for RankData(), splits problem into  subproblems  or  calls
+        basecase code (depending on problem complexity).
+
+        INPUT PARAMETERS:
+            XY      -   array[NPoints,NFeatures], dataset
+            I0      -   index of first row to process
+            I1      -   index of past-the-last row to process;
+                        this function processes half-interval [I0,I1).
+            NFeatures-  number of features
+            IsCentered- whether ranks are centered or not:
+                        * True      -   ranks are centered in such way that  their
+                                        within-row sum is zero
+                        * False     -   ranks are not centered
+            Pool    -   shared pool which holds temporary buffers
+                        (APBuffers structure)
+            BasecaseCost-minimum cost of the problem which will be split
+
+        OUTPUT PARAMETERS:
+            XY      -   data in [I0,I1) are replaced by their within-row ranks;
+                        ranking starts from 0, ends at NFeatures-1
+
+          -- ALGLIB --
+             Copyright 18.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rankdatarec(double[,] xy,
+            int i0,
+            int i1,
+            int nfeatures,
+            bool iscentered,
+            alglib.smp.shared_pool pool,
+            int basecasecost,
+            alglib.xparams _params)
+        {
+            apserv.apbuffers buf0 = null;
+            apserv.apbuffers buf1 = null;
+            double problemcost = 0;
+            int im = 0;
+
+            alglib.ap.assert(i1>=i0, "RankDataRec: internal error");
+            
+            //
+            // Try to activate parallelism
+            //
+            if( i1-i0>=4 && (double)(apserv.rmul3(i1-i0, nfeatures, apserv.logbase2(nfeatures, _params), _params))>=(double)(apserv.smpactivationlevel(_params)) )
+            {
+                if( _trypexec_rankdatarec(xy,i0,i1,nfeatures,iscentered,pool,basecasecost, _params) )
+                {
+                    return;
+                }
+            }
+            
+            //
+            // Recursively split problem, if it is too large
+            //
+            problemcost = apserv.rmul3(i1-i0, nfeatures, apserv.logbase2(nfeatures, _params), _params);
+            if( i1-i0>=2 && (double)(problemcost)>(double)(apserv.spawnlevel(_params)) )
+            {
+                im = (i1+i0)/2;
+                rankdatarec(xy, i0, im, nfeatures, iscentered, pool, basecasecost, _params);
+                rankdatarec(xy, im, i1, nfeatures, iscentered, pool, basecasecost, _params);
+                return;
+            }
+            
+            //
+            // Retrieve buffers from pool, call serial code, return buffers to pool
+            //
+            alglib.smp.ae_shared_pool_retrieve(pool, ref buf0);
+            alglib.smp.ae_shared_pool_retrieve(pool, ref buf1);
+            rankdatabasecase(xy, i0, i1, nfeatures, iscentered, buf0, buf1, _params);
+            alglib.smp.ae_shared_pool_recycle(pool, ref buf0);
+            alglib.smp.ae_shared_pool_recycle(pool, ref buf1);
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_rankdatarec(double[,] xy,
+            int i0,
+            int i1,
+            int nfeatures,
+            bool iscentered,
+            alglib.smp.shared_pool pool,
+            int basecasecost, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+        /*************************************************************************
+        Basecase code for RankData(), performs actual work on subset of data using
+        temporary buffer passed as parameter.
+
+        INPUT PARAMETERS:
+            XY      -   array[NPoints,NFeatures], dataset
+            I0      -   index of first row to process
+            I1      -   index of past-the-last row to process;
+                        this function processes half-interval [I0,I1).
+            NFeatures-  number of features
+            IsCentered- whether ranks are centered or not:
+                        * True      -   ranks are centered in such way that  their
+                                        within-row sum is zero
+                        * False     -   ranks are not centered
+            Buf0    -   temporary buffers, may be empty (this function automatically
+                        allocates/reuses buffers).
+            Buf1    -   temporary buffers, may be empty (this function automatically
+                        allocates/reuses buffers).
+
+        OUTPUT PARAMETERS:
+            XY      -   data in [I0,I1) are replaced by their within-row ranks;
+                        ranking starts from 0, ends at NFeatures-1
+
+          -- ALGLIB --
+             Copyright 18.04.2013 by Bochkanov Sergey
+        *************************************************************************/
+        private static void rankdatabasecase(double[,] xy,
+            int i0,
+            int i1,
+            int nfeatures,
+            bool iscentered,
+            apserv.apbuffers buf0,
+            apserv.apbuffers buf1,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            int i_ = 0;
+
+            alglib.ap.assert(i1>=i0, "RankDataBasecase: internal error");
+            if( alglib.ap.len(buf1.ra0)<nfeatures )
+            {
+                buf1.ra0 = new double[nfeatures];
+            }
+            for(i=i0; i<=i1-1; i++)
+            {
+                for(i_=0; i_<=nfeatures-1;i_++)
+                {
+                    buf1.ra0[i_] = xy[i,i_];
+                }
+                basicstatops.rankx(buf1.ra0, nfeatures, iscentered, buf0, _params);
+                for(i_=0; i_<=nfeatures-1;i_++)
+                {
+                    xy[i,i_] = buf1.ra0[i_];
+                }
+            }
+        }
+
+
+        /*************************************************************************
+        Serial stub for GPL edition.
+        *************************************************************************/
+        public static bool _trypexec_rankdatabasecase(double[,] xy,
+            int i0,
+            int i1,
+            int nfeatures,
+            bool iscentered,
+            apserv.apbuffers buf0,
+            apserv.apbuffers buf1, alglib.xparams _params)
+        {
+            return false;
+        }
+
+
+    }
+    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,
+            alglib.xparams _params)
+        {
+            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 = apserv.rmul2(ns, ns+1, _params)/4;
+            sigma = Math.Sqrt(mu*(2*ns+1)/6);
+            s = (w-mu)/sigma;
+            if( (double)(s)<=(double)(0) )
+            {
+                p = Math.Exp(wsigma(-((w-mu)/sigma), ns, _params));
+                mp = 1-Math.Exp(wsigma(-((w-1-mu)/sigma), ns, _params));
+            }
+            else
+            {
+                mp = Math.Exp(wsigma((w-mu)/sigma, ns, _params));
+                p = 1-Math.Exp(wsigma((w+1-mu)/sigma, ns, _params));
+            }
+            lefttail = Math.Max(p, 1.0E-4);
+            righttail = Math.Max(mp, 1.0E-4);
+            bothtails = 2*Math.Min(lefttail, righttail);
+        }
+
+
+        /*************************************************************************
+        Sequential Chebyshev interpolation.
+        *************************************************************************/
+        private static void wcheb(double x,
+            double c,
+            ref double tj,
+            ref double tj1,
+            ref double r,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            wcheb(x, -5.695528e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.437637e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -2.611906e-01, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -7.625722e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -2.579892e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.086876e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -2.906543e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -2.354881e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 1.007195e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -8.437327e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 26)
+        *************************************************************************/
+        private static double w26(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            wcheb(x, -5.635159e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.395167e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -2.382823e-01, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -6.531987e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -2.060112e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -8.203697e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.516523e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.431364e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 6.384553e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -3.238369e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 27)
+        *************************************************************************/
+        private static double w27(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            wcheb(x, -5.584248e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.359966e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -2.203696e-01, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -5.753344e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.761891e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -7.096897e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.419108e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.581214e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 3.033766e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -5.901441e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 28)
+        *************************************************************************/
+        private static double w28(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            wcheb(x, -5.539163e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.328939e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -2.046376e-01, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -5.061515e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.469271e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -5.711578e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -8.389153e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.250575e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 4.047245e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -5.128555e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 29)
+        *************************************************************************/
+        private static double w29(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            wcheb(x, -5.499756e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.302137e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.915129e-01, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -4.516329e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.260064e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -4.817269e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -5.478130e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.111668e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 4.093451e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -5.135860e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 30)
+        *************************************************************************/
+        private static double w30(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            wcheb(x, -5.464515e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.278342e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.800030e-01, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -4.046294e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.076162e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -3.968677e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.911679e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -8.619185e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 5.125362e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -3.984370e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 40)
+        *************************************************************************/
+        private static double w40(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            wcheb(x, -5.248327e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.136698e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.170982e-01, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.824427e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -3.888648e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.344929e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 2.790407e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -4.619858e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 3.359121e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -2.883026e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 60)
+        *************************************************************************/
+        private static double w60(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            wcheb(x, -5.077191e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.029402e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -7.507931e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -6.506226e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.391278e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -4.263635e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 2.302271e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -2.384348e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 1.865587e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.622355e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 120)
+        *************************************************************************/
+        private static double w120(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            wcheb(x, -4.934426e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -9.433231e-01, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -4.492504e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 1.673948e-05, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -6.077014e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -7.215768e-05, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 9.086734e-05, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -8.447980e-05, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 6.705028e-05, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -5.828507e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 200)
+        *************************************************************************/
+        private static double w200(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            wcheb(x, -4.883080e+00, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -9.132168e-01, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -3.512684e-02, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 1.726342e-03, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -5.189796e-04, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -1.628659e-06, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 4.261786e-05, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -4.002498e-05, ref tj, ref tj1, ref result, _params);
+            wcheb(x, 3.146287e-05, ref tj, ref tj1, ref result, _params);
+            wcheb(x, -2.727576e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S,N), S>=0
+        *************************************************************************/
+        private static double wsigma(double s,
+            int n,
+            alglib.xparams _params)
+        {
+            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, _params);
+            }
+            if( n==6 )
+            {
+                result = w6(s, _params);
+            }
+            if( n==7 )
+            {
+                result = w7(s, _params);
+            }
+            if( n==8 )
+            {
+                result = w8(s, _params);
+            }
+            if( n==9 )
+            {
+                result = w9(s, _params);
+            }
+            if( n==10 )
+            {
+                result = w10(s, _params);
+            }
+            if( n==11 )
+            {
+                result = w11(s, _params);
+            }
+            if( n==12 )
+            {
+                result = w12(s, _params);
+            }
+            if( n==13 )
+            {
+                result = w13(s, _params);
+            }
+            if( n==14 )
+            {
+                result = w14(s, _params);
+            }
+            if( n==15 )
+            {
+                result = w15(s, _params);
+            }
+            if( n==16 )
+            {
+                result = w16(s, _params);
+            }
+            if( n==17 )
+            {
+                result = w17(s, _params);
+            }
+            if( n==18 )
+            {
+                result = w18(s, _params);
+            }
+            if( n==19 )
+            {
+                result = w19(s, _params);
+            }
+            if( n==20 )
+            {
+                result = w20(s, _params);
+            }
+            if( n==21 )
+            {
+                result = w21(s, _params);
+            }
+            if( n==22 )
+            {
+                result = w22(s, _params);
+            }
+            if( n==23 )
+            {
+                result = w23(s, _params);
+            }
+            if( n==24 )
+            {
+                result = w24(s, _params);
+            }
+            if( n==25 )
+            {
+                result = w25(s, _params);
+            }
+            if( n==26 )
+            {
+                result = w26(s, _params);
+            }
+            if( n==27 )
+            {
+                result = w27(s, _params);
+            }
+            if( n==28 )
+            {
+                result = w28(s, _params);
+            }
+            if( n==29 )
+            {
+                result = w29(s, _params);
+            }
+            if( n==30 )
+            {
+                result = w30(s, _params);
+            }
+            if( n>30 )
+            {
+                x = 1.0/n;
+                x0 = 1.0/30;
+                f0 = w30(s, _params);
+                x1 = 1.0/40;
+                f1 = w40(s, _params);
+                x2 = 1.0/60;
+                f2 = w60(s, _params);
+                x3 = 1.0/120;
+                f3 = w120(s, _params);
+                x4 = 1.0/200;
+                f4 = w200(s, _params);
+                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;
+        }
+
+
+    }
+    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,
+            alglib.xparams _params)
+        {
+            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 = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            bothtails = Math.Min(2*binomialdistr.binomialdistribution(Math.Min(gtcnt, necnt-gtcnt), necnt, 0.5, _params), 1.0);
+            lefttail = binomialdistr.binomialdistribution(gtcnt, necnt, 0.5, _params);
+            righttail = binomialdistr.binomialcdistribution(gtcnt-1, necnt, 0.5, _params);
+        }
+
+
+    }
+    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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                bothtails = 2*p;
+                lefttail = p;
+                righttail = 1-p;
+            }
+            else
+            {
+                p = spearmantail(-t, n, _params);
+                bothtails = 2*p;
+                lefttail = 1-p;
+                righttail = p;
+            }
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5)
+        *************************************************************************/
+        private static double spearmantail5(double s,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            if( (double)(s)<(double)(0.000e+00) )
+            {
+                result = studenttdistr.studenttdistribution(3, -s, _params);
+                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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            if( (double)(s)<(double)(1.001e+00) )
+            {
+                result = studenttdistr.studenttdistribution(4, -s, _params);
+                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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            if( (double)(s)<(double)(1.001e+00) )
+            {
+                result = studenttdistr.studenttdistribution(5, -s, _params);
+                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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            if( (double)(s)<(double)(2.001e+00) )
+            {
+                result = studenttdistr.studenttdistribution(6, -s, _params);
+                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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            if( (double)(s)<(double)(2.001e+00) )
+            {
+                result = studenttdistr.studenttdistribution(7, -s, _params);
+                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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+
+            if( n==5 )
+            {
+                result = spearmantail5(-t, _params);
+                return result;
+            }
+            if( n==6 )
+            {
+                result = spearmantail6(-t, _params);
+                return result;
+            }
+            if( n==7 )
+            {
+                result = spearmantail7(-t, _params);
+                return result;
+            }
+            if( n==8 )
+            {
+                result = spearmantail8(-t, _params);
+                return result;
+            }
+            if( n==9 )
+            {
+                result = spearmantail9(-t, _params);
+                return result;
+            }
+            result = studenttdistr.studenttdistribution(n-2, t, _params);
+            return result;
+        }
+
+
+    }
+    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, N>=0
+            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.
+
+        NOTE: this function correctly handles degenerate cases:
+              * when N=0, all p-values are set to 1.0
+              * when variance of X[] is exactly zero, p-values are set
+                to 1.0 or 0.0, depending on difference between sample mean and
+                value of mean being tested.
+
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            double xmean = 0;
+            double x0 = 0;
+            double v = 0;
+            bool samex = new bool();
+            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<=0 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Mean
+            //
+            xmean = 0;
+            x0 = x[0];
+            samex = true;
+            for(i=0; i<=n-1; i++)
+            {
+                v = x[i];
+                xmean = xmean+v;
+                samex = samex && (double)(v)==(double)(x0);
+            }
+            if( samex )
+            {
+                xmean = x0;
+            }
+            else
+            {
+                xmean = xmean/n;
+            }
+            
+            //
+            // Variance (using corrected two-pass algorithm)
+            //
+            xvariance = 0;
+            xstddev = 0;
+            if( n!=1 && !samex )
+            {
+                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) )
+            {
+                if( (double)(xmean)==(double)(mean) )
+                {
+                    bothtails = 1.0;
+                }
+                else
+                {
+                    bothtails = 0.0;
+                }
+                if( (double)(xmean)>=(double)(mean) )
+                {
+                    lefttail = 1.0;
+                }
+                else
+                {
+                    lefttail = 0.0;
+                }
+                if( (double)(xmean)<=(double)(mean) )
+                {
+                    righttail = 1.0;
+                }
+                else
+                {
+                    righttail = 0.0;
+                }
+                return;
+            }
+            
+            //
+            // Statistic
+            //
+            stat = (xmean-mean)/(xstddev/Math.Sqrt(n));
+            s = studenttdistr.studenttdistribution(n-1, stat, _params);
+            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.
+
+        NOTE: this function correctly handles degenerate cases:
+              * when N=0 or M=0, all p-values are set to 1.0
+              * when both samples has exactly zero variance, p-values are set
+                to 1.0 or 0.0, depending on difference between means.
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            bool samex = new bool();
+            bool samey = new bool();
+            double x0 = 0;
+            double y0 = 0;
+            double xmean = 0;
+            double ymean = 0;
+            double v = 0;
+            double stat = 0;
+            double s = 0;
+            double p = 0;
+
+            bothtails = 0;
+            lefttail = 0;
+            righttail = 0;
+
+            if( n<=0 || m<=0 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Mean
+            //
+            xmean = 0;
+            x0 = x[0];
+            samex = true;
+            for(i=0; i<=n-1; i++)
+            {
+                v = x[i];
+                xmean = xmean+v;
+                samex = samex && (double)(v)==(double)(x0);
+            }
+            if( samex )
+            {
+                xmean = x0;
+            }
+            else
+            {
+                xmean = xmean/n;
+            }
+            ymean = 0;
+            y0 = y[0];
+            samey = true;
+            for(i=0; i<=m-1; i++)
+            {
+                v = y[i];
+                ymean = ymean+v;
+                samey = samey && (double)(v)==(double)(y0);
+            }
+            if( samey )
+            {
+                ymean = y0;
+            }
+            else
+            {
+                ymean = ymean/m;
+            }
+            
+            //
+            // S
+            //
+            s = 0;
+            if( n+m>2 )
+            {
+                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) )
+            {
+                if( (double)(xmean)==(double)(ymean) )
+                {
+                    bothtails = 1.0;
+                }
+                else
+                {
+                    bothtails = 0.0;
+                }
+                if( (double)(xmean)>=(double)(ymean) )
+                {
+                    lefttail = 1.0;
+                }
+                else
+                {
+                    lefttail = 0.0;
+                }
+                if( (double)(xmean)<=(double)(ymean) )
+                {
+                    righttail = 1.0;
+                }
+                else
+                {
+                    righttail = 0.0;
+                }
+                return;
+            }
+            
+            //
+            // Statistic
+            //
+            stat = (xmean-ymean)/s;
+            p = studenttdistr.studenttdistribution(n+m-2, stat, _params);
+            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.
+        Equality of variances 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.
+
+        NOTE: this function correctly handles degenerate cases:
+              * when N=0 or M=0, all p-values are set to 1.0
+              * when both samples has zero variance, p-values are set
+                to 1.0 or 0.0, depending on difference between means.
+              * when only one sample has zero variance, test reduces to 1-sample
+                version.
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            int i = 0;
+            bool samex = new bool();
+            bool samey = new bool();
+            double x0 = 0;
+            double y0 = 0;
+            double xmean = 0;
+            double ymean = 0;
+            double xvar = 0;
+            double yvar = 0;
+            double v = 0;
+            double df = 0;
+            double p = 0;
+            double stat = 0;
+            double c = 0;
+
+            bothtails = 0;
+            lefttail = 0;
+            righttail = 0;
+
+            if( n<=0 || m<=0 )
+            {
+                bothtails = 1.0;
+                lefttail = 1.0;
+                righttail = 1.0;
+                return;
+            }
+            
+            //
+            // Mean
+            //
+            xmean = 0;
+            x0 = x[0];
+            samex = true;
+            for(i=0; i<=n-1; i++)
+            {
+                v = x[i];
+                xmean = xmean+v;
+                samex = samex && (double)(v)==(double)(x0);
+            }
+            if( samex )
+            {
+                xmean = x0;
+            }
+            else
+            {
+                xmean = xmean/n;
+            }
+            ymean = 0;
+            y0 = y[0];
+            samey = true;
+            for(i=0; i<=m-1; i++)
+            {
+                v = y[i];
+                ymean = ymean+v;
+                samey = samey && (double)(v)==(double)(y0);
+            }
+            if( samey )
+            {
+                ymean = y0;
+            }
+            else
+            {
+                ymean = ymean/m;
+            }
+            
+            //
+            // Variance (using corrected two-pass algorithm)
+            //
+            xvar = 0;
+            if( n>=2 && !samex )
+            {
+                for(i=0; i<=n-1; i++)
+                {
+                    xvar = xvar+math.sqr(x[i]-xmean);
+                }
+                xvar = xvar/(n-1);
+            }
+            yvar = 0;
+            if( m>=2 && !samey )
+            {
+                for(i=0; i<=m-1; i++)
+                {
+                    yvar = yvar+math.sqr(y[i]-ymean);
+                }
+                yvar = yvar/(m-1);
+            }
+            
+            //
+            // Handle different special cases
+            // (one or both variances are zero).
+            //
+            if( (double)(xvar)==(double)(0) && (double)(yvar)==(double)(0) )
+            {
+                if( (double)(xmean)==(double)(ymean) )
+                {
+                    bothtails = 1.0;
+                }
+                else
+                {
+                    bothtails = 0.0;
+                }
+                if( (double)(xmean)>=(double)(ymean) )
+                {
+                    lefttail = 1.0;
+                }
+                else
+                {
+                    lefttail = 0.0;
+                }
+                if( (double)(xmean)<=(double)(ymean) )
+                {
+                    righttail = 1.0;
+                }
+                else
+                {
+                    righttail = 0.0;
+                }
+                return;
+            }
+            if( (double)(xvar)==(double)(0) )
+            {
+                
+                //
+                // X is constant, unpooled 2-sample test reduces to 1-sample test.
+                //
+                // NOTE: right-tail and left-tail must be passed to 1-sample
+                //       t-test in reverse order because we reverse order of
+                //       of samples.
+                //
+                studentttest1(y, m, xmean, ref bothtails, ref righttail, ref lefttail, _params);
+                return;
+            }
+            if( (double)(yvar)==(double)(0) )
+            {
+                
+                //
+                // Y is constant, unpooled 2-sample test reduces to 1-sample test.
+                //
+                studentttest1(x, n, ymean, ref bothtails, ref lefttail, ref righttail, _params);
+                return;
+            }
+            
+            //
+            // Statistic
+            //
+            stat = (xmean-ymean)/Math.Sqrt(xvar/n+yvar/m);
+            c = xvar/n/(xvar/n+yvar/m);
+            df = apserv.rmul2(n-1, m-1, _params)/((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)), _params);
+            }
+            else
+            {
+                p = 0.5*ibetaf.incompletebeta(df/2, 0.5, df/(df+math.sqr(stat)), _params);
+            }
+            bothtails = 2*Math.Min(p, 1-p);
+            lefttail = p;
+            righttail = 1-p;
+        }
+
+
+    }
+    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].
+
+        NOTE: P-value approximation was  optimized  for  0.0001<=p<=0.2500.  Thus,
+              P's outside of this interval are enforced to these bounds. Say,  you
+              may quite often get P equal to exactly 0.25 or 0.0001.
+
+          -- 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,
+            alglib.xparams _params)
+        {
+            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 = apserv.rmul2(n, m, _params)+apserv.rmul2(n, n+1, _params)*0.5-u;
+            
+            //
+            // Result
+            //
+            mu = apserv.rmul2(n, m, _params)/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(apserv.rmul2(n, m, _params)/ns/(ns-1)*tmp);
+            s = (u-mu)/sigma;
+            if( (double)(s)<=(double)(0) )
+            {
+                p = Math.Exp(usigma(-((u-mu)/sigma), n, m, _params));
+                mp = 1-Math.Exp(usigma(-((u-1-mu)/sigma), n, m, _params));
+            }
+            else
+            {
+                mp = Math.Exp(usigma((u-mu)/sigma, n, m, _params));
+                p = 1-Math.Exp(usigma((u+1-mu)/sigma, n, m, _params));
+            }
+            lefttail = apserv.boundval(Math.Max(mp, 1.0E-4), 0.0001, 0.2500, _params);
+            righttail = apserv.boundval(Math.Max(p, 1.0E-4), 0.0001, 0.2500, _params);
+            bothtails = 2*Math.Min(lefttail, righttail);
+        }
+
+
+        /*************************************************************************
+        Sequential Chebyshev interpolation.
+        *************************************************************************/
+        private static void ucheb(double x,
+            double c,
+            ref double tj,
+            ref double tj1,
+            ref double r,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-6.76984e-01, -6.83700e-01, -6.89873e-01, n2, _params);
+            p2 = uninterpolate(-6.83700e-01, -6.87311e-01, -6.90957e-01, n2, _params);
+            p3 = uninterpolate(-6.89873e-01, -6.90957e-01, -6.92175e-01, n2, _params);
+            result = uninterpolate(p1, p2, p3, n1, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(0.75, N1, N2)
+        *************************************************************************/
+        private static double usigma075(int n1,
+            int n2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-1.44500e+00, -1.45906e+00, -1.47063e+00, n2, _params);
+            p2 = uninterpolate(-1.45906e+00, -1.46856e+00, -1.47644e+00, n2, _params);
+            p3 = uninterpolate(-1.47063e+00, -1.47644e+00, -1.48100e+00, n2, _params);
+            result = uninterpolate(p1, p2, p3, n1, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(1.5, N1, N2)
+        *************************************************************************/
+        private static double usigma150(int n1,
+            int n2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-2.65380e+00, -2.67352e+00, -2.69011e+00, n2, _params);
+            p2 = uninterpolate(-2.67352e+00, -2.68591e+00, -2.69659e+00, n2, _params);
+            p3 = uninterpolate(-2.69011e+00, -2.69659e+00, -2.70192e+00, n2, _params);
+            result = uninterpolate(p1, p2, p3, n1, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(2.25, N1, N2)
+        *************************************************************************/
+        private static double usigma225(int n1,
+            int n2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-4.41465e+00, -4.42260e+00, -4.43702e+00, n2, _params);
+            p2 = uninterpolate(-4.42260e+00, -4.41639e+00, -4.41928e+00, n2, _params);
+            p3 = uninterpolate(-4.43702e+00, -4.41928e+00, -4.41030e+00, n2, _params);
+            result = uninterpolate(p1, p2, p3, n1, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(3.0, N1, N2)
+        *************************************************************************/
+        private static double usigma300(int n1,
+            int n2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-6.89839e+00, -6.83477e+00, -6.82340e+00, n2, _params);
+            p2 = uninterpolate(-6.83477e+00, -6.74559e+00, -6.71117e+00, n2, _params);
+            p3 = uninterpolate(-6.82340e+00, -6.71117e+00, -6.64929e+00, n2, _params);
+            result = uninterpolate(p1, p2, p3, n1, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(3.33, N1, N2)
+        *************************************************************************/
+        private static double usigma333(int n1,
+            int n2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-8.31272e+00, -8.17096e+00, -8.13125e+00, n2, _params);
+            p2 = uninterpolate(-8.17096e+00, -8.00156e+00, -7.93245e+00, n2, _params);
+            p3 = uninterpolate(-8.13125e+00, -7.93245e+00, -7.82502e+00, n2, _params);
+            result = uninterpolate(p1, p2, p3, n1, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(3.66, N1, N2)
+        *************************************************************************/
+        private static double usigma367(int n1,
+            int n2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-9.98837e+00, -9.70844e+00, -9.62087e+00, n2, _params);
+            p2 = uninterpolate(-9.70844e+00, -9.41156e+00, -9.28998e+00, n2, _params);
+            p3 = uninterpolate(-9.62087e+00, -9.28998e+00, -9.11686e+00, n2, _params);
+            result = uninterpolate(p1, p2, p3, n1, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(4.0, N1, N2)
+        *************************************************************************/
+        private static double usigma400(int n1,
+            int n2,
+            alglib.xparams _params)
+        {
+            double result = 0;
+            double p1 = 0;
+            double p2 = 0;
+            double p3 = 0;
+
+            p1 = uninterpolate(-1.20250e+01, -1.14911e+01, -1.13231e+01, n2, _params);
+            p2 = uninterpolate(-1.14911e+01, -1.09927e+01, -1.07937e+01, n2, _params);
+            p3 = uninterpolate(-1.13231e+01, -1.07937e+01, -1.05285e+01, n2, _params);
+            result = uninterpolate(p1, p2, p3, n1, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 5)
+        *************************************************************************/
+        private static double utbln5n5(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -2.412086e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.858542e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.614282e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.372686e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.524731e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.435331e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.284665e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.184141e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.298360e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 7.447272e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.938769e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.276205e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.138481e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.684625e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.558104e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 6)
+        *************************************************************************/
+        private static double utbln5n6(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -2.684429e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.712858e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.009324e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.644391e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 6.034173e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.953498e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.279293e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.563485e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.971952e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.506309e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.541406e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.283205e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.016347e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.221626e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.286752e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 7)
+        *************************************************************************/
+        private static double utbln5n7(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -2.923264e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.506190e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.054280e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.794587e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.726290e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.534180e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.517845e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.904428e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.882443e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.482988e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.114875e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.515082e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.996056e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.293581e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.349444e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 8)
+        *************************************************************************/
+        private static double utbln5n8(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -3.135078e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.247203e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.309697e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.993725e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.567219e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.383704e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.002188e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.487322e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.443899e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.688270e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.600339e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.874948e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.811593e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.072353e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.659457e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 9)
+        *************************************************************************/
+        private static double utbln5n9(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -3.325016e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.939852e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.563029e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.222652e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.195200e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.445665e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.204792e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.775217e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.527781e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.221948e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.242968e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.607959e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.771285e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 6.694026e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.481190e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 10)
+        *************************************************************************/
+        private static double utbln5n10(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -3.496710e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.587658e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.812005e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.427637e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.515702e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.406867e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.796295e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.237591e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.654249e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.181165e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.011665e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.417927e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.534880e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.791255e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.871512e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 11)
+        *************************************************************************/
+        private static double utbln5n11(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -3.652998e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.196503e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.054363e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.618848e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.109411e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.786668e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.215648e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.484220e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.935991e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.396191e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.894177e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.206979e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.519055e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.210326e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.189679e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 12)
+        *************************************************************************/
+        private static double utbln5n12(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -3.796173e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.771177e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.290043e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.794686e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.702110e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.185959e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.416259e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.592056e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.201530e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.754365e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.978945e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.012032e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.304579e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.100378e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.728269e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 13)
+        *************************************************************************/
+        private static double utbln5n13(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -3.928117e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.031605e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.519403e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.962648e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.292183e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.809293e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.465156e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.456278e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.446055e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.109490e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.218256e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.941479e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.058603e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.824402e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.830947e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 14)
+        *************************************************************************/
+        private static double utbln5n14(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.050370e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.083408e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.743164e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.012030e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.884686e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.059656e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.327521e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.134026e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.584201e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.440618e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.524133e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.990007e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.887334e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.534977e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.705395e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 15)
+        *************************************************************************/
+        private static double utbln5n15(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.082033e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.095983e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.814595e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.073148e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.420213e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.517175e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.344180e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.371393e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.711443e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.228569e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.683483e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.267112e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.156044e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 9.131316e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.301023e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 16)
+        *************************************************************************/
+        private static double utbln5n16(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.077482e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.091186e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.797282e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.084994e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.667054e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.843909e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.456732e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.039830e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.723508e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.940608e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.478285e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.649144e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.237703e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.707410e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.874293e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 17)
+        *************************************************************************/
+        private static double utbln5n17(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.071259e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.084700e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.758898e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.073846e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.684838e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.964936e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.782442e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.956362e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.984727e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.196936e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.558262e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.690746e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.364855e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.401006e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.546748e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 18)
+        *************************************************************************/
+        private static double utbln5n18(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.064799e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.077651e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.712659e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.049217e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.571333e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.929809e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.752044e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.949464e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.896101e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.614460e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.384357e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.489113e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.445725e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.945636e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.424653e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 19)
+        *************************************************************************/
+        private static double utbln5n19(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.059159e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.071106e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.669960e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.022780e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.442555e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.851335e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.433865e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.514465e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.332989e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.606099e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.341945e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.402164e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.039761e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.512831e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.284427e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 20)
+        *************************************************************************/
+        private static double utbln5n20(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.054729e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.065747e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.636243e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.003234e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.372789e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.831551e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.763090e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.830626e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.122384e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.108328e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.557983e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.945666e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.965696e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.493236e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.162591e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 21)
+        *************************************************************************/
+        private static double utbln5n21(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.051155e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.061430e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.608869e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.902788e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.346562e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.874709e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.682887e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.026206e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.534551e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.990575e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.713334e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 9.737011e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.304571e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.133110e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.123457e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 22)
+        *************************************************************************/
+        private static double utbln5n22(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.047605e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.057264e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.579513e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.749602e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.275137e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.881768e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.177374e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.981056e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.696290e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.886803e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.085378e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.675242e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.426367e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.039613e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.662378e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 23)
+        *************************************************************************/
+        private static double utbln5n23(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.043761e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.052735e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.544683e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.517503e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.112082e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.782070e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.549483e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.747329e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.694263e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.147141e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.526209e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.039173e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.235615e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.656546e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.014423e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 24)
+        *************************************************************************/
+        private static double utbln5n24(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.040178e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.048355e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.510198e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.261134e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.915864e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.627423e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.307345e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.732992e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.869652e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.494176e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.047533e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.178439e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.424171e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.829195e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.840810e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 25)
+        *************************************************************************/
+        private static double utbln5n25(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.037512e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.044866e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.483269e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.063682e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.767778e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.508540e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.332756e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.881511e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.124041e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.368456e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.930499e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.779630e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.029528e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.658678e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.289695e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 26)
+        *************************************************************************/
+        private static double utbln5n26(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.035915e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.042493e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.466021e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.956432e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.698914e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.465689e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.035254e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.674614e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.492734e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.014021e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.944953e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.255750e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.075841e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.989330e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.134862e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 27)
+        *************************************************************************/
+        private static double utbln5n27(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.034815e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.040650e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.453117e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.886426e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.661702e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.452346e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.002476e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.720126e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.001400e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.729826e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.740640e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.206333e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.366093e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.193471e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.804091e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 28)
+        *************************************************************************/
+        private static double utbln5n28(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.033786e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.038853e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.440281e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.806020e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.612883e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.420436e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.787982e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.535230e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.263121e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.849609e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.863967e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.391610e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.720294e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.952273e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.901413e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 29)
+        *************************************************************************/
+        private static double utbln5n29(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.032834e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.037113e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.427762e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.719146e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.557172e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.375498e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.452033e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.187516e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.916936e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.065533e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.067301e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.615824e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.432244e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.417795e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.710038e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 30)
+        *************************************************************************/
+        private static double utbln5n30(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.032148e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.035679e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.417758e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.655330e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.522132e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.352106e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.326911e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.064969e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.813321e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.683881e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.813346e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.627085e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.832107e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.519336e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.888530e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 5, 100)
+        *************************************************************************/
+        private static double utbln5n100(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.039324e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.022243e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.305825e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.960119e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.112000e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.138868e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.418164e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.174520e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.489617e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.878301e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.302233e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.054113e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.458862e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.186591e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.623412e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 6)
+        *************************************************************************/
+        private static double utbln6n6(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -2.998804e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.681518e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.067578e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.709435e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 9.952661e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.641700e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.304572e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.336275e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.770385e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.401891e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.246148e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.442663e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.502866e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.105855e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.739371e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 7)
+        *************************************************************************/
+        private static double utbln6n7(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -3.274613e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.582352e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.334293e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.915502e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.108091e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.546701e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.298827e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.891501e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.313717e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.989501e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.914594e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.062372e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.158841e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.596443e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.185662e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 8)
+        *************************************************************************/
+        private static double utbln6n8(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -3.520462e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.420299e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.604853e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.165840e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.008756e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.723402e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.843521e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.883405e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.720980e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.301709e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.948034e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.776243e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.623736e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.742068e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.796927e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 9)
+        *************************************************************************/
+        private static double utbln6n9(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -3.741650e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.204487e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.873068e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.446794e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.632286e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.266481e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.280067e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.780687e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.480242e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.592200e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.581019e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.264231e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.347174e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.167535e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.092185e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 10)
+        *************************************************************************/
+        private static double utbln6n10(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -3.942366e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.939896e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.137812e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.720270e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.281070e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.901060e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.824937e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.802812e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.258132e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.233536e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.085530e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.212151e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.001329e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.226048e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.035298e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 11)
+        *************************************************************************/
+        private static double utbln6n11(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.125710e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.063297e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.396852e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.990126e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.927977e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.726500e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.858745e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.654590e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.217736e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.989770e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.768493e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.924364e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.140215e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.647914e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.924802e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 12)
+        *************************************************************************/
+        private static double utbln6n12(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.294250e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.128842e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.650389e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.248611e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.578510e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.162852e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.746982e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.454209e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.128042e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.936650e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.530794e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.665192e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.994144e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.662249e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.368541e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 13)
+        *************************************************************************/
+        private static double utbln6n13(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.450016e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.191088e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.898220e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.050249e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.226901e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.471113e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.007470e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.049420e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.059074e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.881249e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.452780e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.441805e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.787493e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.483957e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.481590e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 14)
+        *************************************************************************/
+        private static double utbln6n14(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.542568e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.226965e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.046029e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.136657e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.786757e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.843748e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.588022e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.253029e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.667188e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.788330e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.474545e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.540494e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.951188e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.863323e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.220904e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 15)
+        *************************************************************************/
+        private static double utbln6n15(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.526567e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.213617e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.975035e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.118480e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.859142e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.083312e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.298720e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.766708e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.026356e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.093113e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.135168e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.136376e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.190870e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.435972e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.413129e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 30)
+        *************************************************************************/
+        private static double utbln6n30(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.427399e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.118239e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.360847e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.745885e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.025041e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.187179e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.432089e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.408451e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.388774e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.795560e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.304136e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.258516e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.180236e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.388679e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.836027e-06, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 6, 100)
+        *************************************************************************/
+        private static double utbln6n100(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.417919e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.094201e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.195883e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.818937e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.514202e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.125047e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.022148e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.284181e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.157766e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.023752e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.127985e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.221690e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.516179e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 9.501398e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 9.380220e-06, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 7)
+        *************************************************************************/
+        private static double utbln7n7(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -3.584790e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.577311e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.617002e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.145186e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.023462e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.408251e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.626515e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.072492e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.722926e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.095445e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.842602e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.751427e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.008927e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.892431e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.772386e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 8)
+        *************************************************************************/
+        private static double utbln7n8(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -3.862154e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.504541e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.900195e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.439995e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.678028e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.485540e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.437047e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.440092e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.114227e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.516569e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.829457e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.787550e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.761866e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.991911e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.533481e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 9)
+        *************************************************************************/
+        private static double utbln7n9(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.112671e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.037277e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.181695e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.765190e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.360116e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.695960e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.780578e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.963843e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.616148e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.852104e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.390744e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.014041e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.888101e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.467474e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.004611e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 10)
+        *************************************************************************/
+        private static double utbln7n10(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.340749e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.118888e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.459730e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.097781e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.057688e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.097406e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.209262e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.065641e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.196677e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.313994e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.827157e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.822284e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.389090e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.340850e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.395172e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 11)
+        *************************************************************************/
+        private static double utbln7n11(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.549783e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.195905e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.733093e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.428447e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.760093e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.431676e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.717152e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.032199e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.832423e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.905979e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.302799e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.464371e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.456211e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.736244e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.140712e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 12)
+        *************************************************************************/
+        private static double utbln7n12(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.564100e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.190813e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.686546e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.395083e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.967359e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.747096e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.304144e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.903198e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.134906e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.175035e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.266224e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.892931e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.604706e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 9.070459e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.427010e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 13)
+        *************************************************************************/
+        private static double utbln7n13(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.532300e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.164642e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.523768e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.531984e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.467857e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.483804e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.524136e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.077740e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.745218e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.602085e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.828831e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.994070e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.873879e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.341937e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.706444e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 14)
+        *************************************************************************/
+        private static double utbln7n14(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.507542e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.143640e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.395755e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.808020e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.044259e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.182308e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.057325e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.724255e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.303900e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.113148e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.102514e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.559442e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.634986e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.776476e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.054489e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 15)
+        *************************************************************************/
+        private static double utbln7n15(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.489960e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.129172e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.316741e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.506107e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.983676e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.258013e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.262515e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.984156e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.912108e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.974023e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 6.056195e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.090842e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.232620e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.816339e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.020421e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 30)
+        *************************************************************************/
+        private static double utbln7n30(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.398705e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.045481e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.821982e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.962304e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.698132e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.062667e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.282353e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.014836e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.035683e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.004137e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.801453e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.920705e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.518735e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.821501e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.801008e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 7, 100)
+        *************************************************************************/
+        private static double utbln7n100(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.386949e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.022834e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.686517e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.323516e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.399392e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.644333e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.617044e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.031396e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.792066e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.675457e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.673416e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.258552e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.174214e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.073644e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.349958e-06, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 8)
+        *************************************************************************/
+        private static double utbln8n8(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.168913e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.051485e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.195325e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.775196e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.385506e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.244902e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.525632e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.771275e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.332874e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.079599e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.882551e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.407944e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.769844e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.062433e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.872535e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 9)
+        *************************************************************************/
+        private static double utbln8n9(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.446939e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.146155e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.488561e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.144561e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.116917e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.205667e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.515661e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.618616e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.599011e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.457324e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.482917e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.488267e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.469823e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.957591e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.058326e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 10)
+        *************************************************************************/
+        private static double utbln8n10(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.700860e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.235253e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.778489e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.527324e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.862885e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.589781e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.507355e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.717526e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 9.215726e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.848696e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.918854e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.219614e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.753761e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.573688e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.602177e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 11)
+        *************************************************************************/
+        private static double utbln8n11(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.812457e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.266153e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.849344e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.971527e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.258944e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.944820e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.894685e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.031836e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.514330e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.351660e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 6.206748e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.492600e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.005338e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.780099e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.673599e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 12)
+        *************************************************************************/
+        private static double utbln8n12(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.762214e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.226296e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.603837e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.643223e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.502438e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.544574e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.647734e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.442259e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.011484e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.384758e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.998259e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.659985e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.331046e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.638478e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.056785e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 13)
+        *************************************************************************/
+        private static double utbln8n13(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.724511e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.195851e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.420511e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.609928e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.893999e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.115919e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.291410e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.339664e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.801548e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.534710e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.793250e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.806718e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.384624e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.120582e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.936453e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 14)
+        *************************************************************************/
+        private static double utbln8n14(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.697171e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.174440e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.300621e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.087393e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.685826e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.085254e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.525658e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.966647e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.453388e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.826066e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.501958e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.336297e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.251972e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.118456e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.415959e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 15)
+        *************************************************************************/
+        private static double utbln8n15(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.674485e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.155941e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.195780e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.544830e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.426183e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.309902e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.650956e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.068874e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.538544e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.192525e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.073905e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.079673e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 9.423572e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 6.579647e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.765904e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 30)
+        *************************************************************************/
+        private static double utbln8n30(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.567159e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.064864e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.688413e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.153712e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.309389e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.226861e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.523815e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.780987e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.166866e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.922431e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.466397e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.690036e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.008185e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.271903e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.534751e-06, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 8, 100)
+        *************************************************************************/
+        private static double utbln8n100(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.547071e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.038129e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.541549e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.525605e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.044992e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.085713e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.017871e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.459226e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.092064e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.024349e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 7.366347e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 6.385637e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.321722e-08, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.439286e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.058079e-07, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 9)
+        *************************************************************************/
+        private static double utbln9n9(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.750859e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.248233e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.792868e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.559372e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.894941e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.643256e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.091370e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.285034e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 6.112997e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.806229e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.150741e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.509825e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.891051e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.485013e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.343653e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 10)
+        *************************************************************************/
+        private static double utbln9n10(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.939333e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.305046e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.935326e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.029141e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.420592e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.053140e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.065930e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.523581e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.544888e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.813741e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.510631e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.536057e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.833815e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.189692e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.615050e-03, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 11)
+        *************************************************************************/
+        private static double utbln9n11(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.867483e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.249072e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.591790e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.400128e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.341992e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.463680e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.487211e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.671196e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.343472e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.544146e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.802335e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.117084e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.217443e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.858766e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.193687e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 12)
+        *************************************************************************/
+        private static double utbln9n12(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.817037e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.209788e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.362108e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.171356e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.661557e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.026141e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.361908e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.093885e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.298389e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.663603e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.768522e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.579015e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.868677e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.440652e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.523037e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 13)
+        *************************************************************************/
+        private static double utbln9n13(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.779308e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.180614e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.196489e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.346621e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.234857e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.796211e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.575715e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.525647e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.964651e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.275235e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.299124e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.397416e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.295781e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.237619e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 7.269692e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 14)
+        *************************************************************************/
+        private static double utbln9n14(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.751545e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.159543e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.086570e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.917446e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.120112e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.175519e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.515473e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.727772e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.070629e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.677569e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.876953e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.233502e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.508182e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.120389e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.847212e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 15)
+        *************************************************************************/
+        private static double utbln9n15(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.727612e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.140634e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.981231e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.382635e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.853575e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.571051e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.567625e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.214197e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.448700e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.712669e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.015050e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.438610e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 6.301363e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.309386e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.164772e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 30)
+        *************************************************************************/
+        private static double utbln9n30(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.615712e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.050023e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.504775e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.318265e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.646826e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.741492e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.735360e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.966911e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.100738e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.348991e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.527687e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.917286e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.397466e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.360175e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.892252e-07, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 9, 100)
+        *************************************************************************/
+        private static double utbln9n100(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.590966e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.021758e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.359849e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.755519e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.533166e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.936659e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.634913e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.730053e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.791845e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.030682e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.228663e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.631175e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.636749e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.404599e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.789872e-07, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 10)
+        *************************************************************************/
+        private static double utbln10n10(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.844398e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.231728e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.486073e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.781321e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.971425e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.215371e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.828451e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.419872e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.430165e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.740363e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.049211e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.269371e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.211393e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.232314e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.016081e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 11)
+        *************************************************************************/
+        private static double utbln10n11(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.782296e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.184732e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.219585e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.457012e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.296008e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.481501e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.527940e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.953426e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.563840e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.574403e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.535775e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.338037e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.002654e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.852676e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.318132e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 12)
+        *************************************************************************/
+        private static double utbln10n12(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.737458e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.150952e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.036884e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.609030e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.908684e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.439666e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.162647e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.451601e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.148757e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.803981e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.731621e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.346903e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.013151e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.956148e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.438381e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 13)
+        *************************************************************************/
+        private static double utbln10n13(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.702863e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.124829e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.897428e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.979802e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.634368e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.180461e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.484926e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.864376e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.186576e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.886925e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.836828e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.074756e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.209547e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.883266e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.380143e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 14)
+        *************************************************************************/
+        private static double utbln10n14(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.676124e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.104740e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.793826e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.558886e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.492462e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.052903e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.917782e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.878696e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.576046e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.764551e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.288778e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.757658e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.299101e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.265197e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.384503e-07, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 15)
+        *************************************************************************/
+        private static double utbln10n15(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.654247e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.088083e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.705945e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.169677e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.317213e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.264836e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.548024e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.633910e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.505621e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.658588e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.320254e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.175277e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.122317e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.675688e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.661363e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 30)
+        *************************************************************************/
+        private static double utbln10n30(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.548099e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.004444e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.291014e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.523674e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.828211e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.716917e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.894256e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.433371e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.522675e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.764192e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.140235e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.629230e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.541895e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.944946e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.726360e-06, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 10, 100)
+        *************************************************************************/
+        private static double utbln10n100(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.522316e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.769627e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.158110e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.053650e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.242235e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.173571e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.033661e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.824732e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.084420e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.610036e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.728155e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.217130e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.340966e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.001235e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.694052e-07, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 11)
+        *************************************************************************/
+        private static double utbln11n11(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.880694e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.200698e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.174092e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.072304e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.054773e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.506613e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.813942e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.223644e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.417416e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.499166e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.194332e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 7.369096e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.968590e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.630532e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.061000e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 12)
+        *************************************************************************/
+        private static double utbln11n12(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.832622e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.165420e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.987306e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.265621e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.723537e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.347406e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.353464e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 6.613369e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.102522e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.237709e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.665652e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.626903e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.167518e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.564455e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.047320e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 13)
+        *************************************************************************/
+        private static double utbln11n13(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.796242e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.138769e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.851739e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.722104e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.548304e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.176683e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.817895e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.842451e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.935870e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.421777e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.238831e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.867026e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.458255e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.306259e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.961487e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 14)
+        *************************************************************************/
+        private static double utbln11n14(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.766969e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.117082e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.739574e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.238865e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.350306e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.425871e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.640172e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.660633e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.879883e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.349658e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.271795e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.304544e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.024201e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.816867e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.596787e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 15)
+        *************************************************************************/
+        private static double utbln11n15(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.743570e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.099705e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.650612e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.858285e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.187036e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.689241e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.294360e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.072623e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.278008e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.322382e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.131558e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.305669e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.825627e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.332689e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.120973e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 30)
+        *************************************************************************/
+        private static double utbln11n30(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.627440e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.011333e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.224126e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.232856e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.859347e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.377381e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.756709e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.033230e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.875472e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.608399e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.102943e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.740693e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.343139e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.196878e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.658062e-07, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 11, 100)
+        *************************************************************************/
+        private static double utbln11n100(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.596486e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.814761e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.085187e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.766529e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.379425e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.986351e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.214705e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.360075e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.260869e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.033307e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.727087e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.393883e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.242989e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.111928e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.898823e-09, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12, 12)
+        *************************************************************************/
+        private static double utbln12n12(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.786627e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.132099e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.817523e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.570179e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.479511e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.799492e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.565350e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.530139e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.380132e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.242761e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.576269e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.018771e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.933911e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 9.002799e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.022048e-06, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12, 13)
+        *************************************************************************/
+        private static double utbln12n13(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.750794e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.105988e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.684754e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.011826e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.262579e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.044492e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.478741e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.322165e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.621104e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.068753e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.468396e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.056235e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.327375e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.914877e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.784191e-04, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12, 14)
+        *************************************************************************/
+        private static double utbln12n14(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.722404e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.085254e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.579439e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.563738e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.066730e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.129346e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.014531e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.129679e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.000909e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.996174e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 6.377924e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.936304e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.051098e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 9.025820e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 8.730585e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12, 15)
+        *************************************************************************/
+        private static double utbln12n15(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.700008e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.068971e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.499725e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.250897e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.473145e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.680008e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.483350e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.766992e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.891081e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.015140e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.977756e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.707414e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.114786e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 6.238865e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.381445e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12, 30)
+        *************************************************************************/
+        private static double utbln12n30(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.585782e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.838583e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.103394e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.834015e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.635212e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.948212e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.574169e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.747980e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.833672e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.722433e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.181038e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.206473e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.716003e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.476434e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.217700e-07, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 12, 100)
+        *************************************************************************/
+        private static double utbln12n100(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.553481e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.541334e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.701907e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.414757e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.404103e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.234388e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.453762e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.311060e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.317501e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.713888e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.309583e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.019804e-08, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.224829e-09, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.349019e-08, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.893302e-08, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 13, 13)
+        *************************************************************************/
+        private static double utbln13n13(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.859047e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.130164e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.689719e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.950693e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.231455e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.976550e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.538455e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.245603e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.142647e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.831434e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.032483e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.488405e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.156927e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.949279e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.532700e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 13, 14)
+        *************************************************************************/
+        private static double utbln13n14(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.828341e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.108110e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.579552e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.488307e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.032328e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.988741e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.766394e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.388950e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.338179e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.133440e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.023518e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.110570e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.202332e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.056132e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.536323e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 13, 15)
+        *************************************************************************/
+        private static double utbln13n15(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.803952e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.090686e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.495310e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.160314e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.073124e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.480313e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.478239e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.140914e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.311541e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.677105e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.115464e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.578563e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.044604e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.888939e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 2.395644e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 13, 30)
+        *************************************************************************/
+        private static double utbln13n30(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.678434e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.995491e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.078100e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.705220e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.258739e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.671526e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.185458e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.507764e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.411446e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -4.044355e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.285765e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.345282e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.066940e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.962037e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.723644e-07, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 13, 100)
+        *************************************************************************/
+        private static double utbln13n100(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.640804e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.671552e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.364990e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.274444e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.047440e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.161439e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.171729e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.562171e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.359762e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.275494e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.747635e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.700292e-08, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.565559e-09, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 5.005396e-09, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 3.335794e-09, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 14, 14)
+        *************************************************************************/
+        private static double utbln14n14(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.798584e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.087107e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.478532e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.098050e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.855986e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.409083e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.299536e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.176177e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.479417e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.812761e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -5.225872e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 4.516521e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 6.730551e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 9.237563e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.611820e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 14, 15)
+        *************************************************************************/
+        private static double utbln14n15(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.774668e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.070267e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.399348e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.807239e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.845763e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.071773e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.261698e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.011695e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.305946e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.879295e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.999439e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.904438e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.944986e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.373908e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.140794e-05, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 14, 30)
+        *************************************************************************/
+        private static double utbln14n30(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.649587e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.807829e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.989753e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.463646e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.586580e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -6.745917e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.635398e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.923172e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.446699e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.613892e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.214073e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.651683e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.272777e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.464988e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.109803e-07, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, 14, 100)
+        *************************************************************************/
+        private static double utbln14n100(double s,
+            alglib.xparams _params)
+        {
+            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, _params);
+            ucheb(x, -4.610577e+00, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -9.482675e-01, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.605550e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.062151e-02, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.525154e-03, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.835983e-04, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -8.411440e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.744901e-05, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.318850e-06, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.692100e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -1.536270e-07, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -3.705888e-08, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -7.999599e-09, ref tj, ref tj1, ref result, _params);
+            ucheb(x, -2.908395e-09, ref tj, ref tj1, ref result, _params);
+            ucheb(x, 1.546923e-09, ref tj, ref tj1, ref result, _params);
+            return result;
+        }
+
+
+        /*************************************************************************
+        Tail(S, N1, N2)
+        *************************************************************************/
+        private static double usigma(double s,
+            int n1,
+            int n2,
+            alglib.xparams _params)
+        {
+            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, _params);
+                }
+                if( Math.Max(n1, n2)==6 )
+                {
+                    result = utbln5n6(s, _params);
+                }
+                if( Math.Max(n1, n2)==7 )
+                {
+                    result = utbln5n7(s, _params);
+                }
+                if( Math.Max(n1, n2)==8 )
+                {
+                    result = utbln5n8(s, _params);
+                }
+                if( Math.Max(n1, n2)==9 )
+                {
+                    result = utbln5n9(s, _params);
+                }
+                if( Math.Max(n1, n2)==10 )
+                {
+                    result = utbln5n10(s, _params);
+                }
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln5n11(s, _params);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln5n12(s, _params);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln5n13(s, _params);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln5n14(s, _params);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln5n15(s, _params);
+                }
+                if( Math.Max(n1, n2)==16 )
+                {
+                    result = utbln5n16(s, _params);
+                }
+                if( Math.Max(n1, n2)==17 )
+                {
+                    result = utbln5n17(s, _params);
+                }
+                if( Math.Max(n1, n2)==18 )
+                {
+                    result = utbln5n18(s, _params);
+                }
+                if( Math.Max(n1, n2)==19 )
+                {
+                    result = utbln5n19(s, _params);
+                }
+                if( Math.Max(n1, n2)==20 )
+                {
+                    result = utbln5n20(s, _params);
+                }
+                if( Math.Max(n1, n2)==21 )
+                {
+                    result = utbln5n21(s, _params);
+                }
+                if( Math.Max(n1, n2)==22 )
+                {
+                    result = utbln5n22(s, _params);
+                }
+                if( Math.Max(n1, n2)==23 )
+                {
+                    result = utbln5n23(s, _params);
+                }
+                if( Math.Max(n1, n2)==24 )
+                {
+                    result = utbln5n24(s, _params);
+                }
+                if( Math.Max(n1, n2)==25 )
+                {
+                    result = utbln5n25(s, _params);
+                }
+                if( Math.Max(n1, n2)==26 )
+                {
+                    result = utbln5n26(s, _params);
+                }
+                if( Math.Max(n1, n2)==27 )
+                {
+                    result = utbln5n27(s, _params);
+                }
+                if( Math.Max(n1, n2)==28 )
+                {
+                    result = utbln5n28(s, _params);
+                }
+                if( Math.Max(n1, n2)==29 )
+                {
+                    result = utbln5n29(s, _params);
+                }
+                if( Math.Max(n1, n2)>29 )
+                {
+                    f0 = utbln5n15(s, _params);
+                    f1 = utbln5n30(s, _params);
+                    f2 = utbln5n100(s, _params);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2), _params);
+                }
+                return result;
+            }
+            
+            //
+            // N1=6, N2 = 6, 7, 8, ...
+            //
+            if( Math.Min(n1, n2)==6 )
+            {
+                if( Math.Max(n1, n2)==6 )
+                {
+                    result = utbln6n6(s, _params);
+                }
+                if( Math.Max(n1, n2)==7 )
+                {
+                    result = utbln6n7(s, _params);
+                }
+                if( Math.Max(n1, n2)==8 )
+                {
+                    result = utbln6n8(s, _params);
+                }
+                if( Math.Max(n1, n2)==9 )
+                {
+                    result = utbln6n9(s, _params);
+                }
+                if( Math.Max(n1, n2)==10 )
+                {
+                    result = utbln6n10(s, _params);
+                }
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln6n11(s, _params);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln6n12(s, _params);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln6n13(s, _params);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln6n14(s, _params);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln6n15(s, _params);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln6n15(s, _params);
+                    f1 = utbln6n30(s, _params);
+                    f2 = utbln6n100(s, _params);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2), _params);
+                }
+                return result;
+            }
+            
+            //
+            // N1=7, N2 = 7, 8, ...
+            //
+            if( Math.Min(n1, n2)==7 )
+            {
+                if( Math.Max(n1, n2)==7 )
+                {
+                    result = utbln7n7(s, _params);
+                }
+                if( Math.Max(n1, n2)==8 )
+                {
+                    result = utbln7n8(s, _params);
+                }
+                if( Math.Max(n1, n2)==9 )
+                {
+                    result = utbln7n9(s, _params);
+                }
+                if( Math.Max(n1, n2)==10 )
+                {
+                    result = utbln7n10(s, _params);
+                }
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln7n11(s, _params);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln7n12(s, _params);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln7n13(s, _params);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln7n14(s, _params);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln7n15(s, _params);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln7n15(s, _params);
+                    f1 = utbln7n30(s, _params);
+                    f2 = utbln7n100(s, _params);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2), _params);
+                }
+                return result;
+            }
+            
+            //
+            // N1=8, N2 = 8, 9, 10, ...
+            //
+            if( Math.Min(n1, n2)==8 )
+            {
+                if( Math.Max(n1, n2)==8 )
+                {
+                    result = utbln8n8(s, _params);
+                }
+                if( Math.Max(n1, n2)==9 )
+                {
+                    result = utbln8n9(s, _params);
+                }
+                if( Math.Max(n1, n2)==10 )
+                {
+                    result = utbln8n10(s, _params);
+                }
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln8n11(s, _params);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln8n12(s, _params);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln8n13(s, _params);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln8n14(s, _params);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln8n15(s, _params);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln8n15(s, _params);
+                    f1 = utbln8n30(s, _params);
+                    f2 = utbln8n100(s, _params);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2), _params);
+                }
+                return result;
+            }
+            
+            //
+            // N1=9, N2 = 9, 10, ...
+            //
+            if( Math.Min(n1, n2)==9 )
+            {
+                if( Math.Max(n1, n2)==9 )
+                {
+                    result = utbln9n9(s, _params);
+                }
+                if( Math.Max(n1, n2)==10 )
+                {
+                    result = utbln9n10(s, _params);
+                }
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln9n11(s, _params);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln9n12(s, _params);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln9n13(s, _params);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln9n14(s, _params);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln9n15(s, _params);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln9n15(s, _params);
+                    f1 = utbln9n30(s, _params);
+                    f2 = utbln9n100(s, _params);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2), _params);
+                }
+                return result;
+            }
+            
+            //
+            // N1=10, N2 = 10, 11, ...
+            //
+            if( Math.Min(n1, n2)==10 )
+            {
+                if( Math.Max(n1, n2)==10 )
+                {
+                    result = utbln10n10(s, _params);
+                }
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln10n11(s, _params);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln10n12(s, _params);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln10n13(s, _params);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln10n14(s, _params);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln10n15(s, _params);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln10n15(s, _params);
+                    f1 = utbln10n30(s, _params);
+                    f2 = utbln10n100(s, _params);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2), _params);
+                }
+                return result;
+            }
+            
+            //
+            // N1=11, N2 = 11, 12, ...
+            //
+            if( Math.Min(n1, n2)==11 )
+            {
+                if( Math.Max(n1, n2)==11 )
+                {
+                    result = utbln11n11(s, _params);
+                }
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln11n12(s, _params);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln11n13(s, _params);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln11n14(s, _params);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln11n15(s, _params);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln11n15(s, _params);
+                    f1 = utbln11n30(s, _params);
+                    f2 = utbln11n100(s, _params);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2), _params);
+                }
+                return result;
+            }
+            
+            //
+            // N1=12, N2 = 12, 13, ...
+            //
+            if( Math.Min(n1, n2)==12 )
+            {
+                if( Math.Max(n1, n2)==12 )
+                {
+                    result = utbln12n12(s, _params);
+                }
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln12n13(s, _params);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln12n14(s, _params);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln12n15(s, _params);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln12n15(s, _params);
+                    f1 = utbln12n30(s, _params);
+                    f2 = utbln12n100(s, _params);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2), _params);
+                }
+                return result;
+            }
+            
+            //
+            // N1=13, N2 = 13, 14, ...
+            //
+            if( Math.Min(n1, n2)==13 )
+            {
+                if( Math.Max(n1, n2)==13 )
+                {
+                    result = utbln13n13(s, _params);
+                }
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln13n14(s, _params);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln13n15(s, _params);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln13n15(s, _params);
+                    f1 = utbln13n30(s, _params);
+                    f2 = utbln13n100(s, _params);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2), _params);
+                }
+                return result;
+            }
+            
+            //
+            // N1=14, N2 = 14, 15, ...
+            //
+            if( Math.Min(n1, n2)==14 )
+            {
+                if( Math.Max(n1, n2)==14 )
+                {
+                    result = utbln14n14(s, _params);
+                }
+                if( Math.Max(n1, n2)==15 )
+                {
+                    result = utbln14n15(s, _params);
+                }
+                if( Math.Max(n1, n2)>15 )
+                {
+                    f0 = utbln14n15(s, _params);
+                    f1 = utbln14n30(s, _params);
+                    f2 = utbln14n100(s, _params);
+                    result = uninterpolate(f0, f1, f2, Math.Max(n1, n2), _params);
+                }
+                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, _params);
+                s1 = 7.500000e-01;
+                f1 = usigma075(n1, n2, _params);
+                s2 = 1.500000e+00;
+                f2 = usigma150(n1, n2, _params);
+                s3 = 2.250000e+00;
+                f3 = usigma225(n1, n2, _params);
+                s4 = 3.000000e+00;
+                f4 = usigma300(n1, n2, _params);
+                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, _params);
+                s1 = 3.333333e+00;
+                f1 = usigma333(n1, n2, _params);
+                s2 = 3.666667e+00;
+                f2 = usigma367(n1, n2, _params);
+                s3 = 4.000000e+00;
+                f3 = usigma400(n1, n2, _params);
+                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 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:
+            P           -   p-value for the test
+
+        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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            p = jarqueberaapprox(n, s, _params);
+        }
+
+
+        private static void jarqueberastatistic(double[] x,
+            int n,
+            ref double s,
+            alglib.xparams _params)
+        {
+            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,
+            alglib.xparams _params)
+        {
+            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, _params));
+                }
+                if( n==6 )
+                {
+                    result = Math.Exp(jbtbl6(x, _params));
+                }
+                if( n==7 )
+                {
+                    result = Math.Exp(jbtbl7(x, _params));
+                }
+                if( n==8 )
+                {
+                    result = Math.Exp(jbtbl8(x, _params));
+                }
+                if( n==9 )
+                {
+                    result = Math.Exp(jbtbl9(x, _params));
+                }
+                if( n==10 )
+                {
+                    result = Math.Exp(jbtbl10(x, _params));
+                }
+                if( n==11 )
+                {
+                    result = Math.Exp(jbtbl11(x, _params));
+                }
+                if( n==12 )
+                {
+                    result = Math.Exp(jbtbl12(x, _params));
+                }
+                if( n==13 )
+                {
+                    result = Math.Exp(jbtbl13(x, _params));
+                }
+                if( n==14 )
+                {
+                    result = Math.Exp(jbtbl14(x, _params));
+                }
+                if( n==15 )
+                {
+                    result = Math.Exp(jbtbl15(x, _params));
+                }
+                if( n==16 )
+                {
+                    result = Math.Exp(jbtbl16(x, _params));
+                }
+                if( n==17 )
+                {
+                    result = Math.Exp(jbtbl17(x, _params));
+                }
+                if( n==18 )
+                {
+                    result = Math.Exp(jbtbl18(x, _params));
+                }
+                if( n==19 )
+                {
+                    result = Math.Exp(jbtbl19(x, _params));
+                }
+                if( n==20 )
+                {
+                    result = Math.Exp(jbtbl20(x, _params));
+                }
+                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, _params);
+                f2 = jbtbl30(x, _params);
+                f3 = jbtbl50(x, _params);
+                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, _params);
+                f2 = jbtbl65(x, _params);
+                f3 = jbtbl100(x, _params);
+                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, _params);
+                f2 = jbtbl130(x, _params);
+                f3 = jbtbl200(x, _params);
+                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, _params);
+                f2 = jbtbl301(x, _params);
+                f3 = jbtbl501(x, _params);
+                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, _params);
+                f2 = jbtbl701(x, _params);
+                f3 = jbtbl1401(x, _params);
+                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, _params)+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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -2.854501e-20, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.756616e-20, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.075941e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -9.772272e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.175686e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.576162e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.126861e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.434425e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.790359e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.809178e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.479704e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.717040e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.294170e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.880632e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.023344e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.601531e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.920403e-02, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -5.700471e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.425764e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -2.011104e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.168177e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.085666e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.738606e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.022876e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.462402e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.908270e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.230772e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.006996e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.410222e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.893768e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 8.114564e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -3.578700e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.394664e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.928290e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.813273e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.076063e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.835380e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.013013e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.058903e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.856915e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.710887e-03, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.695911e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.473192e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.203236e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.590379e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.291876e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.132007e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 9.411147e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.180067e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.487610e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.436561e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -2.772675e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.707912e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.691171e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.132795e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.481310e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.867536e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 8.772327e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.033387e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.378277e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.497964e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.636814e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -9.581640e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -8.140472e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.682053e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.568561e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.933930e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.140472e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.895025e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.140472e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.933930e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.568561e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.682053e+00, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.095921e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.736828e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.047438e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.484320e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.937923e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.810470e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.139780e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.708443e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -7.802461e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.547593e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.241042e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.203274e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.201990e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.125597e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.584426e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.546069e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -3.137533e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.016671e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.745637e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.189801e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.621610e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.741122e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.516368e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.552085e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.787029e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.359774e-03, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -9.277151e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.669339e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.086149e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.333816e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.871249e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.007048e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.482245e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.355615e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -7.972248e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.747737e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.808530e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.888305e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 9.001302e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.378767e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.108510e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.915372e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -2.845231e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.809956e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.543461e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.880397e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.160074e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.356527e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.394428e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 9.619892e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.758763e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.790977e-05, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -6.562730e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.353934e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.069933e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.849151e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 8.931406e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.636295e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.178340e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.917749e-05, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -9.962401e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.838715e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.055792e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.580316e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.781701e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.770362e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.838983e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.999052e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.877029e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.734650e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.249254e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.320250e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.432266e-03, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -6.051558e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.000992e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.022547e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -9.808401e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.592870e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.575081e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.086173e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.089011e-05, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.068388e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.179661e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.555524e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.238964e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.364320e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.895771e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.762774e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.201340e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.684579e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 8.299519e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.606261e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.310869e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.320115e-03, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -3.657836e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.047209e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.319599e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.545631e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 9.280445e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.815679e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.213519e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.256838e-05, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.515287e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.611880e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.271311e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.495815e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.141186e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.180886e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.388211e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.890761e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.233175e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.946156e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -2.034157e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.878986e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.078603e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.990977e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.866215e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.897866e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.512252e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.073743e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.022621e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.501343e-03, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -3.557541e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -9.459092e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.044145e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.546132e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.002374e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.349456e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.025669e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.590242e-05, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.467539e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.270774e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.075763e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.611647e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.990785e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 8.109212e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.135031e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.915919e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.522390e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.144701e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.920809e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.175858e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.002049e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.158966e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.157781e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.762172e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.780347e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.193310e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.442421e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.547756e-03, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -3.479081e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.640733e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.466899e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.469485e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.150009e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.965975e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.710210e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.327808e-05, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.421365e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.960468e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.149167e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.361109e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.976022e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.082700e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.563328e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.453123e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.917559e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.151067e-05, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -2.010441e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.566146e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.129690e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.929724e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.524227e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.192933e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.254730e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.620685e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.289618e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.112350e-03, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.361653e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.009497e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.951784e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.377903e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.003253e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.271309e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -8.349578e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 9.476514e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.717385e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.222591e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.635124e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.815993e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.655936e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 8.404310e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.663794e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 8.868618e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.381447e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 9.444801e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.581503e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -9.468696e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.728509e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.206470e-03, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.298141e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.832803e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.877026e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.539436e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 8.439658e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.756911e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -8.242944e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.020002e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.130531e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.512373e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.054876e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.556839e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.832655e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.340545e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.361143e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.283219e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.484549e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.805968e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.057243e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.454439e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.177513e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.819209e-03, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.810330e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.840039e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.337294e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.383549e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.556515e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.656965e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.404569e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.447867e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -6.222920e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.146667e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.809176e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.057028e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.211838e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.099683e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.161105e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.225465e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.917838e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.455980e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.999589e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.604263e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.484445e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.819937e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.930390e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.771761e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.232581e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.029083e-04, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.762373e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.598890e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.189437e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.971721e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.823067e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.064501e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.014932e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.953513e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -6.070918e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.277196e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.879817e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.887357e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.638451e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.502800e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.165796e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.034960e-05, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.496296e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.002227e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.338250e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.137036e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.586202e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -9.736384e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.332251e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.877982e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.160963e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.547247e-03, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.719633e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.459123e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.034878e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.113868e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.030922e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.054022e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.525623e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.277360e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -5.977749e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.223716e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.363889e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.711774e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.557257e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.254794e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 9.034207e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.498107e-05, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.430689e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.136575e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.726627e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.421110e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.581510e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.559520e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.838208e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 8.428839e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.170682e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.006647e-04, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.948947e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.632184e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.139397e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.006237e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.810031e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.573620e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 9.951242e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.274092e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.464196e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.882139e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.575144e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.822804e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.061348e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.908404e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.978353e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.327151e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.346404e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.840051e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.578551e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -9.813886e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.905973e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.358489e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.450795e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.941157e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.432418e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.070537e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 9.375654e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.367378e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 9.890859e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.679782e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -7.487737e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.244254e-02, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.724298e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.872756e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.658268e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.573597e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.994157e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.994825e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.394303e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.785029e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.990264e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.037838e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.755546e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.774473e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.821395e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.392603e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.353313e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.197018e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.396848e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.804293e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.867928e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.768758e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.211792e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.925799e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.046235e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -9.536469e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.489642e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -6.177316e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.590637e-02, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.519711e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.148699e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.001204e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.207620e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.034778e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.220322e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.033260e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.588280e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.851653e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.287733e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.189127e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.429738e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.058822e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 9.086776e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.445783e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.311671e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.261298e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.496987e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.605249e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 8.162282e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -5.888603e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.080113e-02, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.434631e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.514580e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 7.332038e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.158197e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.121233e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.051056e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.214233e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.487977e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.424720e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.116715e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.043152e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.718149e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.313701e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.097305e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.181031e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.256975e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -5.895179e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.933237e-02, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.313418e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.628931e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.264287e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.518487e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.499826e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.836044e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.279690e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.665746e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.290012e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.487632e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.704465e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.211669e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -6.399767e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.498208e-02, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.253864e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.618032e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.112729e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.210546e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.732602e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.410527e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.331990e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.779129e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.674749e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.669077e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.679136e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 8.833221e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -6.475304e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.116734e-02, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.177398e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.297970e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.869745e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.717288e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.982108e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.427636e-05, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.455006e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.942996e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.973795e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.418812e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.156778e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.896705e-05, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -7.152176e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.725393e-02, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.125800e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -9.595847e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.219666e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.502210e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.414543e-05, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.754115e-05, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.582060e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.004472e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -4.709092e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.105779e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.197391e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -8.386780e-04, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -7.918763e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.626584e-02, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.079765e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -5.463005e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 6.875659e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.740694e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.044502e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.746714e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 3.810594e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.197111e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -8.846221e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.386405e-02, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.059769e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -3.922680e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 4.847054e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.860007e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.963942e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.838711e-02, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -2.893112e-04, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.159788e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -9.817020e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.383727e-02, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            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, _params);
+                jbcheb(x, -1.030061e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.259222e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 2.536254e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -2.095443e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 1.759363e-01, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -7.751359e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -6.124368e-03, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, -1.793114e-03, ref tj, ref tj1, ref result, _params);
+                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, _params);
+                jbcheb(x, -1.225382e+00, ref tj, ref tj1, ref result, _params);
+                jbcheb(x, 5.392349e-02, ref tj, ref tj1, ref result, _params);
+                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,
+            alglib.xparams _params)
+        {
+            double t = 0;
+
+            r = r+c*tj;
+            t = 2*x*tj1-tj;
+            tj = tj1;
+            tj1 = t;
+        }
+
+
+    }
+    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,
+            alglib.xparams _params)
+        {
+            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, _params)-fdistr.fdistribution(df1, df2, stat, _params));
+            lefttail = fdistr.fdistribution(df1, df2, xvar/yvar, _params);
+            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,
+            alglib.xparams _params)
+        {
+            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, _params);
+            bothtails = 2*Math.Min(s, 1-s);
+            lefttail = s;
+            righttail = 1-lefttail;
+        }
+
+
+    }
+}
+
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/ALGLIB License-3.15.0.txt
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/ALGLIB License-3.15.0.txt	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/ALGLIB License-3.15.0.txt	(revision 17158)
@@ -0,0 +1,1034 @@
+﻿ALGLIB® - numerical analysis library, 1999-2019.
+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
+
+
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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; 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.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
+
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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, either version 3 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.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/ALGLIB changes by HEAL.txt
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/ALGLIB changes by HEAL.txt	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/ALGLIB changes by HEAL.txt	(revision 17158)
@@ -0,0 +1,6 @@
+ALGLIB changes by HEAL
+
+ap.cs(ln 765)
+mkommend: added thread static attribute to RNG to have a separate instance per thread and allow modification of the seed
+
+ALGLIB 3.7.0 contains further changes that have not been ported yet
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/HeuristicLab.ALGLIB-3.15.0.csproj
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/HeuristicLab.ALGLIB-3.15.0.csproj	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/HeuristicLab.ALGLIB-3.15.0.csproj	(revision 17158)
@@ -0,0 +1,113 @@
+﻿<?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>{6CAB3410-EBD4-40D9-ABD6-AA95BB684C65}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>HeuristicLab.ALGLIB</RootNamespace>
+    <AssemblyName>HeuristicLab.ALGLIB-3.15.0</AssemblyName>
+    <TargetFrameworkVersion>v4.6.1</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>
+    <Prefer32Bit>false</Prefer32Bit>
+  </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>
+    <Prefer32Bit>false</Prefer32Bit>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <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>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="HeuristicLab.snk" />
+    <None Include="Plugin.cs.frame" />
+    <None Include="Properties\AssemblyInfo.cs.frame" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="ALGLIB changes by HEAL.txt" />
+    <Content Include="ALGLIB License-3.15.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 Condition=" '$(OS)' == 'Windows_NT' ">set Path=%25Path%25;$(ProjectDir);$(SolutionDir)
+set ProjectDir=$(ProjectDir)
+set SolutionDir=$(SolutionDir)
+set Outdir=$(Outdir)
+
+call PreBuildEvent.cmd
+</PreBuildEvent>
+    <PreBuildEvent Condition=" '$(OS)' != 'Windows_NT' ">
+export ProjectDir=$(ProjectDir)
+export SolutionDir=$(SolutionDir)
+
+$SolutionDir/PreBuildEvent.sh
+</PreBuildEvent>
+  </PropertyGroup>
+</Project>
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/Plugin.cs.frame
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/Plugin.cs.frame	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/Plugin.cs.frame	(revision 17158)
@@ -0,0 +1,35 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2019 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.15.0.$WCREV$")]  
+  [PluginFile("HeuristicLab.ALGLIB-3.15.0.dll", PluginFileType.Assembly)]
+  [PluginFile("ALGLIB-3.15.0.dll", PluginFileType.Assembly)]
+  [PluginFile("ALGLIB License-3.15.0.txt", PluginFileType.License)]
+  public class HeuristicLabAlgLibPlugin : PluginBase {
+  }
+}
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/Properties/AssemblyInfo.cs.frame
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/Properties/AssemblyInfo.cs.frame	(revision 17158)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.15.0/HeuristicLab.ALGLIB-3.15.0/Properties/AssemblyInfo.cs.frame	(revision 17158)
@@ -0,0 +1,56 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2019 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.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-2019 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)]
+
+
+// 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.15.0")]
+[assembly: AssemblyFileVersion("3.15.0.$WCREV$")]
Index: /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.7.0/HeuristicLab.ALGLIB-3.7.0/HeuristicLab.ALGLIB-3.7.0.csproj
===================================================================
--- /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.7.0/HeuristicLab.ALGLIB-3.7.0/HeuristicLab.ALGLIB-3.7.0.csproj	(revision 17157)
+++ /stable/HeuristicLab.ExtLibs/HeuristicLab.ALGLIB/3.7.0/HeuristicLab.ALGLIB-3.7.0/HeuristicLab.ALGLIB-3.7.0.csproj	(revision 17158)
@@ -127,4 +127,5 @@
   </ItemGroup>
   <ItemGroup>
+    <Content Include="ALGLIB changes by HEAL.txt" />
     <Content Include="ALGLIB License-3.7.0.txt">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
Index: /stable/HeuristicLab.Scripting/3.3/Script.cs
===================================================================
--- /stable/HeuristicLab.Scripting/3.3/Script.cs	(revision 17157)
+++ /stable/HeuristicLab.Scripting/3.3/Script.cs	(revision 17158)
@@ -29,8 +29,9 @@
 using System.Reflection;
 using System.Text;
+using System.Text.RegularExpressions;
+using HEAL.Attic;
 using HeuristicLab.Common;
 using HeuristicLab.Common.Resources;
 using HeuristicLab.Core;
-using HEAL.Attic;
 using Microsoft.CSharp;
 
@@ -122,5 +123,9 @@
 
     public virtual IEnumerable<Assembly> GetAssemblies() {
-      var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.IsDynamic && File.Exists(a.Location)).ToList();
+      var assemblies = AppDomain.CurrentDomain.GetAssemblies()
+        .Where(a => !a.IsDynamic && File.Exists(a.Location))
+        .GroupBy(x => Regex.Replace(Path.GetFileName(x.Location), @"-[\d.]+\.dll$", ""))
+        .Select(x => x.OrderByDescending(y => y.GetName().Version).First())
+        .ToList();
       assemblies.Add(typeof(Microsoft.CSharp.RuntimeBinder.Binder).Assembly); // for dlr functionality
       return assemblies;
